Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Multimedia (https://www.delphipraxis.net/16-multimedia/)
-   -   TImages verwalten (https://www.delphipraxis.net/182231-timages-verwalten.html)

Schwedenbitter 10. Okt 2014 17:16

TImages verwalten
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo,

ich habe ein Programm zum Scannen. Dieses legt die Bilder zunächst nacheinander in einer
Delphi-Quellcode:
TImageList
ab und speichert die dann als png-Dateien.

Jetzt möchte ich das Programm gern erweitern. Dazu sollen die Bilder statt in einer TImageList jeweils erst als
Delphi-Quellcode:
TImage
nebst 3
Delphi-Quellcode:
TRadioButtons
in einer
Delphi-Quellcode:
TScrollBox
untereinander angezeigt werden. Hintergrund ist der, dass die Bilder standardmäßig in schwarzweiß eingescannt werden. Allerdings soll/muss es hin und wieder auch mal Fotos geben. Auch diesen Fall löst mein Programm im Ansatz. Allerdings gibt es immer wieder Nutzer, die einfachen Text mit 16 Mio. Farben einscannen und damit sinnloser Weise riesige Dateien erzeugen. Die Bilder sollen daher selbst beim Einscannen in Farbe nachträglich umgewandelt werden können - dafür die TRadioButtons.
Ein Screenshot, wie ich es gern hätte, ist angehängt.

Wie man TImage und TRadioButton zur Laufzeit erzeugt, ist klar. Klar ist mehr oder weniger auch die Umwandlung. Es geht also wirklich nur um das Vorhalten/Anzeigen für den Benutzer zur Laufzeit.

Meine Überlegung geht jetzt dahin, Image und Buttons in ein Object zu packen und dann das ganze in einer Liste abgeleitet von
Delphi-Quellcode:
TStringList
zu organisieren. In dieser kann man mit
Delphi-Quellcode:
AddObject();
derartiges erledigen.

Wäre das völlig abwegig?
Falls ja, wie macht man es richtig[tm]?

Ich lese gern selbst. Im Moment fehlen mir aber die passenden Suchworte für mein Problem. Daher bitte nicht gleich meckern, falls das Problem schon einmal ausführlich behandelt wurde, sondern lieber eine Links setzen. Danke.

Gruß, Alex

Sir Rufo 10. Okt 2014 18:18

AW: TImages verwalten
 
Zunächst würde ich die Bilder nicht komplett im Speicher behalten sondern jeden Scan direkt auf die Platte schreiben und merken (in einer Liste).

Für eine Komponenten-Komposition (TImage, TRadioButtons) eignen sich Frames sehr schön. Einfach ein Frame bauen und dann für jeden Scan eine Instanz erzeugen und in die Scrollbox packen.

Die Bilder werden für die Ansicht ja nicht in der vollen Auflösung benötigt, somit wäre es auch ratsam die Bilder selber nur in einer reduzierten Auflösung für die Anzeige bereitzustellen. Mit einer PaintBox könnte man auch auf gecachte Bilder zurückgreifen, die im Hintergrund geladen werden.

Auch das Umrechnen der Bilder sollte in einem Thread erfolgen, somit musst du eh mit Threads hantieren ;)

Schwedenbitter 11. Okt 2014 09:16

AW: TImages verwalten
 
Liste der Anhänge anzeigen (Anzahl: 1)
Danke für die Antwort.
Zitat:

Zitat von Sir Rufo (Beitrag 1275583)
Zunächst würde ich die Bilder nicht komplett im Speicher behalten sondern jeden Scan direkt auf die Platte schreiben und merken (in einer Liste).

Das ist mehr als unhandlich. Das Behalten der Bilder im Speicher ist über 5 Jahr erprobt und klappt prima. Denn die meisten Bilder in schwarzweiß sind bei einer Auflösung von 200dpi kleiner als 1 MB und der Scanner liefert 60 Seiten/min. Wenn ich da speichere, halte ich den Arbeitsablauf auf.

Zitat:

Zitat von Sir Rufo (Beitrag 1275583)
Für eine Komponenten-Komposition (TImage, TRadioButtons) eignen sich Frames sehr schön. Einfach ein Frame bauen und dann für jeden Scan eine Instanz erzeugen und in die Scrollbox packen.

Ich hatte mir das gestern Abend mal angesehen. Das sieht sehr schwierig aus. Ich habe aber evtl. eine Lösung gefunden, an deren Umsetzung es aber praktisch noch hapert.

Zitat:

Zitat von Sir Rufo (Beitrag 1275583)
Die Bilder werden für die Ansicht ja nicht in der vollen Auflösung benötigt, somit wäre es auch ratsam die Bilder selber nur in einer reduzierten Auflösung für die Anzeige bereitzustellen. Mit einer PaintBox könnte man auch auf gecachte Bilder zurückgreifen, die im Hintergrund geladen werden.

Auch das Umrechnen der Bilder sollte in einem Thread erfolgen, somit musst du eh mit Threads hantieren ;)

Zum ersten - Siehe oben.
Zum zweiten - das kommt noch :-D

Und nun zu meiner Lösung:
Ich hatte bei der Suche nach Frames im Netz die Möglichkeit entdeckt, Formulare anzudocken. Das klappt prima in einer
Delphi-Quellcode:
TPageControl
. Und der Vorteil (mit Frames kenne ich mich nicht aus) ist der, dass ich die Formulare schon zu Entwurfszeit basteln kann. Ich habe jetzt bloß noch ein Problem:
Wie schaffe ich es in der TPageControl auf das Schließen eines Fensters zu reagieren, so dass ich den TabSheet ebenfalls schließen kann? Ich habe meinen Code mal angehängt.

Wenn dazu jemand eine Lösung hätte, wäre mein Problem - wenn auch anders als in der Überschrift evtl. gedacht - gelöst.

Gruß und danke für den Push in diese Richtung!
Alex

Sir Rufo 11. Okt 2014 09:57

AW: TImages verwalten
 
Die Anwendung wird nicht aufgehalten, wenn die Aktionen in einem Thread erfolgen (speichern, laden, umrechnen) ;)

Zum Schließen des Tabs musst du eigentlich rein gar nichts machen ... ok bis auf eins:
Delphi-Quellcode:
type
  TDockForm = clas( TForm )
  protected
    // Ich könnte auch den Event OnClose nehmen, da ich aber von dieser Form
    // weitere ableiten möchte, ist dieser Weg etwas robuster :o)
    procedure DoClose( var Action: TCloseAction ); override;
  end;

  TDockFormClass = class of TDockForm;

procedure TDockForm.DoClose( var Action: TCloseAction );
begin
  Action := caFree;
  inherited; // <- dort wird der Event OnClose gefeuert

end;
Wenn du jetzt das Formular schließt, dann wird es durch das Setzen von
Delphi-Quellcode:
Action := caFree
auch automatisch zerstört, was wiederum automatisch den Tab aus dem PageControl entfernt :)

Vorausgesetzt, die Form wird auch so dort eingedockt:
Delphi-Quellcode:
procedure TMyForm.DockNewForm( APageControl : TPageControl; ADockFormClass : TDockFormClass );
var
  LForm : TDockForm;
begin
  LForm := TDockFormClass.Create( APageControl );
  try
    LForm.ManualDock( APgeControl, nil, alClient );
    LForm.Visible := True;
    LForm := nil;
  finally
    LForm.Free;
  end;
end;
Wenn du diese
Delphi-Quellcode:
TDockForm
als Basis für deine weiteren DockFOrms nimmst, dann kannst du nach Belieben dort die unterschiedlichsten DockForms an dein PageControl hängen und diese werden dann beim Schließen automatisch aufgeräumt und der Tab geschlossen :)

Schwedenbitter 11. Okt 2014 11:58

AW: TImages verwalten
 
Zitat:

Zitat von Sir Rufo (Beitrag 1275595)
Vorausgesetzt, die Form wird auch so dort eingedockt:
Delphi-Quellcode:
procedure TMyForm.DockNewForm( APageControl : TPageControl; ADockFormClass : TDockFormClass );
var
  LForm : TDockForm;
begin
  LForm := TDockFormClass.Create( APageControl );
  try
    LForm.ManualDock( APgeControl, nil, alClient );
    LForm.Visible := True;
    LForm := nil;
  finally
    LForm.Free;
  end;
end;
...

Danke für die ausführlich Beschreibung.
Ich kann aber nirgends ein Ereignis wie
Delphi-Quellcode:
onDockNewForm
finden. Und dann habe ich noch das Problem wo bzw. wie ist
Delphi-Quellcode:
TDockFormClass
definiert?

[edit]
Und noch ein Problem fällt mir ein:
Wie greife ich auf die einzelnen Komponenten (TImage etc.) nach dem Erstellen des angedockten Forms zu?
Der Benutzer soll die Bilder erst scannen. Dann mit dem Button Bilder, die er nicht haben will, löschen können und schließlich über einen zentralen Save-Button alle Bilder nacheinander abspeichern.
Letzteres soll dann über einen Thread vom Hauptformular erfolgen. Ich muss also irgendwie rankommen...
[/edit]

Gruß, Alex

Sir Rufo 11. Okt 2014 14:04

AW: TImages verwalten
 
Zitat:

Zitat von Schwedenbitter (Beitrag 1275599)
Ich kann aber nirgends ein Ereignis wie
Delphi-Quellcode:
onDockNewForm
finden.

Wieso ein Ereignis? Das ist eine Methode, die ich geschrieben habe ... die allerdings besser die Form-Instanz zurückliefert
Delphi-Quellcode:
procedure TMyForm.DockNewForm( APageControl : TPageControl; ADockFormClass : TDockFormClass ) : TDockForm;
var
  LForm : TDockForm;
begin
  LForm := TDockFormClass.Create( APageControl );
  try
    LForm.ManualDock( APgeControl, nil, alClient );
    LForm.Visible := True;
    Result := LForm;
    LForm := nil;
  finally
    LForm.Free;
  end;
end;
Zitat:

Zitat von Schwedenbitter (Beitrag 1275599)
Und dann habe ich noch das Problem wo bzw. wie ist
Delphi-Quellcode:
TDockFormClass
definiert?

steht doch in dem CodeSchnipsel
Delphi-Quellcode:
type
  TDockForm = class( TForm )
  protected
    // Ich könnte auch den Event OnClose nehmen, da ich aber von dieser Form
    // weitere ableiten möchte, ist dieser Weg etwas robuster :o)
    procedure DoClose( var Action: TCloseAction ); override;
  end;

  TDockFormClass = class of TDockForm; // <--- DA
Zitat:

Zitat von Schwedenbitter (Beitrag 1275599)
[edit]
Und noch ein Problem fällt mir ein:
Wie greife ich auf die einzelnen Komponenten (TImage etc.) nach dem Erstellen des angedockten Forms zu?
Der Benutzer soll die Bilder erst scannen. Dann mit dem Button Bilder, die er nicht haben will, löschen können und schließlich über einen zentralen Save-Button alle Bilder nacheinander abspeichern.
Letzteres soll dann über einen Thread vom Hauptformular erfolgen. Ich muss also irgendwie rankommen...
[/edit]

Was interessiert es dich, was in dem
Delphi-Quellcode:
TImage
drin ist? Das wird für die Anzeige benötigt ... aus und Ende.

Erstelle dir ein Daten-Objekt, was alle Informationen beinhaltet und merke dir dort alle Informationen zu jedem gescannten Bild und organisiere diese in einer Liste. Zu jedem Daten-Objekt gibt es dann eine Form (der du das zugehörige Daten-Objekt an die Hand gibts), die die Inhalte aus dem Daten-Objekt anzeigen und auch die Eingaben des Benutzers dort ablegen kann.

Wenn der Benutzer fertig ist, dann gehts du einfach durch deine Liste und arbeitest die einzelnen Bilder ab. Die einzelnen Forms interessieren nicht mehr ;)

Schwedenbitter 14. Okt 2014 17:16

AW: TImages verwalten
 
Danke für die Erläuterungen.
Zitat:

Zitat von Sir Rufo (Beitrag 1275609)
... Das ist eine Methode, die ich geschrieben habe ... die allerdings besser die Form-Instanz zurückliefert
Delphi-Quellcode:
procedure TMyForm.DockNewForm( APageControl : TPageControl; ADockFormClass : TDockFormClass ) : TDockForm;
var
  LForm : TDockForm;
begin
  LForm := TDockFormClass.Create( APageControl );
  try
    LForm.ManualDock( APgeControl, nil, alClient );
    LForm.Visible := True;
    Result := LForm;
    LForm := nil;
  finally
    LForm.Free;
  end;
end;

OK.
Aber müsste es nicht richtiger Weise dann
Delphi-Quellcode:
function
statt
Delphi-Quellcode:
procedure
heißen?
Und wo genau wird die Instanz übergeben (Result:=... oder Var in der Definition der Procedure)?

Zitat:

Zitat von Sir Rufo (Beitrag 1275609)
Was interessiert es dich, was in dem
Delphi-Quellcode:
TImage
drin ist? Das wird für die Anzeige benötigt ... aus und Ende.

Es interessiert mich deshalb, weil ich damit gern arbeiten würde. Es soll 4 Möglichkeiten geben, die Bilder zu speichern. Über den Button soll das Bild gelöscht werden können. Und damit schließe ich (und geben den Speicher frei) dann zunächst erst einmal bloß die Form. Ich will/muss also nicht nur an die Bilddaten, sondern auch herausbekommen, welches der RadioButtons gewählt wurde. Und das sollte theoretisch über das Code-Beispiel funktionieren, wenn ich Deine Antwort richtig interpretiere. Ich verstehe bloß nicht wie.

Zitat:

Zitat von Sir Rufo (Beitrag 1275609)
Erstelle dir ein Daten-Objekt, was alle Informationen beinhaltet und merke dir dort alle Informationen zu jedem gescannten Bild und organisiere diese in einer Liste. Zu jedem Daten-Objekt gibt es dann eine Form (der du das zugehörige Daten-Objekt an die Hand gibts), die die Inhalte aus dem Daten-Objekt anzeigen und auch die Eingaben des Benutzers dort ablegen kann.

Wenn der Benutzer fertig ist, dann gehts du einfach durch deine Liste und arbeitest die einzelnen Bilder ab. Die einzelnen Forms interessieren nicht mehr ;)

Genau das war/ist ja meine (Ausgangs)Frage. Wie organisiere ich diese Liste? Leite ich sie von TStringList ab? Oder einfach bloß von TList?... Mit TImageList arbeite ich im Moment schon. Aber das ist mir zu unkomfortabel.

mkinzler 14. Okt 2014 17:20

AW: TImages verwalten
 
Zitat:

Aber müsste es nicht richtiger Weise dann function statt procedure heißen?
Ja.
Zitat:

Und wo genau wird die Instanz übergeben (Result:=... oder Var in der Definition der Procedure)?
Als Result nur im gezeigten Code wird diese vorher freigegeben ( im finally), was imho keinen Sinn macht.

Sir Rufo 14. Okt 2014 18:40

AW: TImages verwalten
 
Zitat:

Zitat von mkinzler (Beitrag 1275911)
Als Result nur im gezeigten Code wird diese vorher freigegeben ( im finally), was imho keinen Sinn macht.

Die Form-Instanz wird eben nicht freigegeben, ausser es kommt innerhalb der Methode zu einer Exception, dann wird abgebrochen, demzufolge nichts zurückgegeben und darum durch das
Delphi-Quellcode:
finally
die Instanz freigegeben.

Das ist eigentlich eine sehr übliche und sinnvolle Vorgehensweise wenn man nicht auf Speicherlecks steht.
Delphi-Quellcode:
function FooFactory : TFoo;
var
  LFoo : TFoo;
begin
  LFoo := TFoo.Create;
  try
    ...
    Result := Lfoo;
    LFoo := nil; // auf nil setzen
  finally
    // bei einer Exception wird die Instanz freigegeben,
    // sonst nicht, denn dann zeigt LFoo ja auf nil ;o)
    LFoo.Free;
  end;
end;
Auch wenn es sich im Beispiel um eine Komponente mit Owner handelt, was für eine korrekte Freigabe durch den Owner sorgt. Eine ungenutzte Instanz im Speicher ist zuviel und solche Fehler (Speicher läuft im Betrieb voll) sind nachher nur schwer zu finden, denn beim Beenden gibt es kein Speicherleck, dafür ist irgendwann der Speicher voll und die Anwendung verabschiedet sich.

Schwedenbitter 14. Okt 2014 19:20

AW: TImages verwalten
 
Danke. Es wird langsam klarer und zeigt sich, dass es für Gelegenheitprogrammierer wie mich schwer werden dürfte. Ich habe jetzt folgendes zusammengebastelt. Allerdings weiß ich nicht, wie ich herausbekomme, ob der Benutzer ein Form selbst mit Close "zerstört" hat:
Delphi-Quellcode:
Type
   TMainForm   = Class(TForm)
                     BtnAdd   : TButton;
                     BtnInfo   : TButton;
                     PCPics   : TPageControl;
                     Procedure FormCreate(Sender: TObject);
                     Procedure BtnAddClick(Sender: TObject);
                     Procedure BtnInfoClick(Sender: TObject);
                     Procedure FormDestroy(Sender: TObject);
                 Protected
                     fCount   : Integer;
                     fPicList   : TList;
                 End;

Var
   MainForm      : TMainForm;

Implementation

{$R *.dfm}

Procedure TMainForm.FormCreate(Sender: TObject);
Begin
   fCount:=0;
   fPicList:=TList.Create;
End;

Procedure TMainForm.BtnAddClick(Sender: TObject);
Var
   aPNG         : TPngImage;
   lForm         : TDockForm;
Begin
   aPNG:=TPngImage.Create;
   Try
      aPNG.LoadFromFile('.TestBilder\_0001.png');
      aPNG.RemoveTransparency;
      lForm:=TDockFormClass.Create(PCPics);
      Try
         lForm.ManualDock(PCPics, nil, alClient);
         lForm.Visible:=True;
         LForm.IMGScan.Picture.Bitmap.Assign(aPNG);
         Inc(fCount);
         lForm.Caption:='Bild ' + FormatFloat('0,', fCount);
         fPicList.Add(lForm);   // <- in Liste merken
         lForm:=nil;            // 1:1 übernommen
      Finally
         lForm.Free;            // 1:1 übernommen
         PCPics.ActivePageIndex:=Pred(PCPics.PageCount);
      End;
   Finally
      aPNG.Free;
   End;
End;

Procedure TMainForm.BtnInfoClick(Sender: TObject);
Var
   I            : Integer;
   lDockForm         : TDockForm;
Begin
   If (fPicList.Count = 0) Then Exit;
   I:=PCPics.ActivePageIndex;
   lDockForm:=fPicList.Items[I];// <- so greife ich jetzt zu.
   With lDockForm Do
   Try
      ShowMessage('Original' + #09 + BoolToStr(RBorgjpg.Checked, True));
   Finally
   End;
End;

Procedure TMainForm.FormDestroy(Sender: TObject);
Begin
   Try
      While (fPicList.Count > 0) Do fPicList.Delete(0);
   Finally
      fPicList.Free;
   End;
End;

End.
[edit]
Also der Zugriff klappt.
Bleibt die Frage, wie ich herausbekomme, ob der Benutzer eine Form gelöscht hat...
[/edit]

himitsu 14. Okt 2014 20:54

AW: TImages verwalten
 
Zitat:

Zitat von Sir Rufo (Beitrag 1275928)
Die Form-Instanz wird eben nicht freigegeben, ausser es kommt innerhalb der Methode zu einer Exception, dann wird abgebrochen,
Delphi-Quellcode:
LFoo := TFoo.Create;
try
  ...
  Result := Lfoo;
  LFoo := nil; // auf nil setzen
finally
  // bei einer Exception wird die Instanz freigegeben,
  // sonst nicht, denn dann zeigt LFoo ja auf nil ;o)
  LFoo.Free;
end;

Warum schreibt man den Code dann nicht so, wie das, was er machen soll und sparrt dabei auch gleich noch die leicht verwirrende doppelte Variable?
Delphi-Quellcode:
Result := TFoo.Create;
try
  ...
except
  Result.Free;
  raise;
end;

Sir Rufo 14. Okt 2014 21:53

AW: TImages verwalten
 
Ich habe das doch so geschrieben, wie das was er machen soll. Und
Delphi-Quellcode:
try finally
ist ein Ressourcen-Schutzblock - wenn ich mich da richtig erinnere - und genau den will ich haben und darum schreibe ich ihn so.

himitsu 15. Okt 2014 09:14

AW: TImages verwalten
 
Zitat:

Zitat von Sir Rufo (Beitrag 1275940)
das was er machen soll.

Also bei einem Fehler eine Aktion ausführen? :mrgreen:

OK, es funktioniert Beides, aber IMHO ist es mit'm Finally etwas schneller missverständlicher.

Delphi-Quellcode:
LFoo := TFoo.Create;
try
  ...
  LFoo := nil; // Womöglich auch noch tief in irgendwelchen IFs versteckt auf nil setzen.
  ...
finally
  LFoo.Free; // IMMER freigeben, außer irgendwo anders wurde vorher die Variable heimlich auf nil gesetzt.
end;
Delphi-Quellcode:
Result := TFoo.Create;
try
  ...
except
  Result.Free; // NUR freigeben, wenn es geknallt hat.
  raise;
end;
Ich weiß, im Falle von Free muß nicht geprüft werden, da es das selber macht, aber irgenwie sollte man hier schon, beim Free, einen Hinweis geben ... sei es in Form eines Codes oder als Kommentar, daß sich oben irgendwo noch ein :=nil versteckt.
Delphi-Quellcode:
LFoo := TFoo.Create;
try
  ...
  LFoo := nil;
  ...
finally
  if Assigned(LFoo) then
    LFoo.Free;
end;

Sir Rufo 15. Okt 2014 10:33

AW: TImages verwalten
 
Das werde ich definitiv nicht machen, denn ich schreibe keine Kommentare, die sich auf Basis-Funktionalitäten (
Delphi-Quellcode:
TObject.Free
prüft selber auf assigned) beziehen, noch füge ich unnützen Code hinzu
Delphi-Quellcode:
if (Assigned( LFoo ) then LFoo.Free;
). Ich schreibe keine Tutorials sondern Anwendungen und wer das verstehen möchte, der soll die Sprache beherrschen.

Wie man Instanzen aufräumt sollte eigentlich bekannt sein und gehört zum Basiswissen:
Delphi-Quellcode:
procedure Example;
var
  LFoo : TFoo;
  LBar : TBar;
begin
  LFoo := nil;
  LBar := nil;
  try
    LFoo := TFoo.Create;

    while LFoo.NeedsMore do
    begin
      LBar := TBar.Create;
      LFoo.InteractWith( LBar );
      FreeAndNil( LBar );
    end;

  finally
    LFoo.Free;
    LBar.Free;
  end;

himitsu 15. Okt 2014 12:14

AW: TImages verwalten
 
Dein
Delphi-Quellcode:
finally LFoo.Free; end;
wird immer ausgeführt, macht aber meistens (hoffentlich immer) nichts, was schon einen Kommentar wert wäre. :stupid:

Schwedenbitter 15. Okt 2014 12:42

AW: TImages verwalten
 
Danke für Eure erhellenden Antworten. Natürlich lerne ich gern dazu. Einen "Krieg" anzuzetteln, war das Letzte, was ich mit meiner Fragestellung wollte.
Zitat:

Zitat von Sir Rufo (Beitrag 1275981)
...
Wie man Instanzen aufräumt sollte eigentlich bekannt sein und gehört zum Basiswissen:
...

Vermutlich bezog sich das auf mich. Und damit muss ich zugeben, dass mir Basiswissen fehlt. In der Regel lasse ich über meinen "fertigen" Code FastMM drüber laufen und evtl. Speicherlecks anzeigen. Bislang habe ich noch immer aufräumen können. Ich bin mir auch nicht wirklich sicher, ob wir nicht vom Selben reden. Denn das übliche Konstrukt ist mir trotzdem bekannt:
Delphi-Quellcode:
Procedure Example;
Var
   SL         : TIrgendwas;
Begin
   SL:=TIrgendwas.Create;
   Try
      SL.TuEtwas;
   Finally
      SL.Free;
   End;
End;
Ich habe jetzt aber konkret das Problem, dass meine (Object)Liste und die tatsächlich in den TabSheets vorhandenen Forms auseinanderfallen können. Denn mir fehlt immer noch das Wissen und auch eine Idee, wie die angedockte Form den Umstand des Schließens und damit das Löschen aus dem Tabsheet an das Hauptprgramm/-form weiterleitet.
Die Änderung des TabSheets wird mir über
Delphi-Quellcode:
TPageControl.OnChange
mitgeteilt. Aber wie filtere ich jetzt das verschwundene Fenster aus meiner Liste? Eine Idee wäre natürlich einen Zugriff zu machen, damit dann künstlich eine Exception zu erzeugen und das betreffende Fenster zu löschen; so klappt es jedenfalls. Ich kann mir aber kaum vorstellen, dass das so legitim ist:
Delphi-Quellcode:
Procedure TMainForm.PCPicsChange(Sender: TObject);
Var
   I            : Integer;
   lDockForm   : TDockForm;
Begin
   I:=0;
   While (fPicList.Count > I) Do
   Begin
      lDockForm:=fPicList.Items[I];
      Try
         lDockForm.RBorgjpg.Checked;
         Inc(I);   // <- hat geklappt, ist also noch vorhanden
      Except
         fPicList.Delete(I);
      End;
   End;
End;
Daher eben nochmal meine Frage, wie man das macht richtig?

Gruß, Alex

Jumpy 15. Okt 2014 13:21

AW: TImages verwalten
 
Die ganzen DockFormen werden ja im Hauptformular/Programm kreiert.
Da könntest du denen doch eine Callback-Funktion mitgeben, die im OnClose des DockFormulars ausgelöst wird. Methodenzeiger ist hier glaub ich das Stichwort.

Sir Rufo 15. Okt 2014 14:22

AW: TImages verwalten
 
Richtiger wäre es den ganzen Kram zu trennen.

Es gibt eine Liste mit Datenobjekten
Delphi-Quellcode:
// ein Datenobjekt
TImageData = class
  property Filename : string;
end;
Für das DatenObjekt gibt es eine Form, die dieses DatenObjekt anzeigen kann
Delphi-Quellcode:
TImageView = class( TDockForm )
  property ImageData : TImageData;
end;
Die MainForm kann nun aus der Liste mit den DatenObjekten die passenden Forms erzeugen und natürlich auch wieder entfernen.
Delphi-Quellcode:
TMainForm = class( TForm )
  FDataList : TObjectList;
  FViewList : TObjectList;
  procedure PresentDataList;
end;

procedure TMainForm.PresentDataList;
var
  LView : TImageView;
begin
  for LIdx := 0 to FDataList.Count - 1 do
  begin
    if LIdx < FViewList.Count then
      LView := FViewList[LIdx] as TImageView
    else
      begin
        LView := TImageView.Create( PageControl1 );
        FViewList.Add( LView );
        LView.ManualDock( PageControl1, nil, alClient );
      end;
    LView.Visible := false;
    LView.ImageData := FDataList[LIdx] as TImageData;
    LView.Visible := true;
  end;
  // überflüssige Views entfernen
  for LIdx := FViewList.Count - 1 downto FDataList.Count do
  begin
    LView := FViewList[LIdx] as TImageView;
    FViewList.Delete( LIdx );
    LView.Close;
  end;
end;
Nach jeder Änderung an der
Delphi-Quellcode:
FDataList
(Sortieren, Hinzufügen, Entfernen) ruft man einfach
Delphi-Quellcode:
PresentDataList
auf und der Keks ist gegessen.

Auf der MainForm ist nun ein Button um das aktuell angezeigte Bild zu entfernen, dann muss man folgendes ausführen
Delphi-Quellcode:
procedure TMainForm.Button1Click( Sender : TObject );
begin
  FDataList.Delete( PageControl1.TabIndex );
  PresentDataList;
end;
Richter wäre es aber auch hier eine sprechendere Programmierung zu wählen
Delphi-Quellcode:
procedure TMainForm.Button1Click( Sender : TObject );
begin
  RemoveCurrentItem;
end;

procedure TMainForm.RemoveCurrentItem;
begin
  RemoveItem( FDataList[PageControl1.TabIndex] );
end;

procedure TMainForm.RemoveItem( AItem : TObject );
begin
  FDataList.Remove( AItem );
  PresentDataList;
end;
Wenn man nämlich aus der ImageView heraus dieses Entfernen auslösen möchte, dann gibt man jeder ImageView einfach dieses als Event mit
Delphi-Quellcode:
procedure TMainForm.PresentDataList;
  ...
  LView.OnRemoveItem := RemoveItem;
  ...
und in der ImageView wird das dann einfach aufgerufen und alles passiert von selber
Delphi-Quellcode:
procedure TImageView.RemoveButtonClick( Sender : TObject );
begin
  OnRemoveItem( ImageData );
end;
Das möchte ich jetzt aber nicht weiter ausführen, sonst sprengt es den Rahmen.

Die
Delphi-Quellcode:
TImageView
Form muss jetzt natürlich im
Delphi-Quellcode:
OnShow
Event die Daten aus dem
Delphi-Quellcode:
ImageData
lesen und entsprechend anzeigen. Hier kann man in dem Setter von ImageData auch noch ein Flag setzen, ob sich ImageData überhaupt geändert hat und kann sich das erneute Einlesen sparen.

(da das mit dem Resourcenschutzblock nur zu Verwirrung führt, habe ich mal alles weggelassen, was nicht unbedingt notwendig ist)

Blup 15. Okt 2014 14:22

AW: TImages verwalten
 
Mir ist nicht klar für was du überhaupt ein angedocktes Fenster benötigst.
Ich vermute du vermischt die Anzeige und das Halten der Daten.

Wenn zum Bild zusätzliche Einstellungen gehören, dann erweitere einfach die Klasse.
Delphi-Quellcode:
TMyColorStyle = (mycsDefault, mycsGray, mycsBlackAndWhite);

TMyPicture = class(TPicture)
private
  FColorStyle: TMyColorStyle;
protected
  procedure Draw(ACanvas: TCanvas; const Rect: TRect); override;
  procedure DrawBlackAndWhite((ACanvas: TCanvas; const Rect: TRect);
  procedure DrawGray(ACanvas: TCanvas; const Rect: TRect);
published
  property ColorStyle: TMyColorStyle read FColorStyle write FColorStyle;
end;

TMyPictureList = TObjectList<TMyPicture>;

procedure TMyPicture.Draw(ACanvas: TCanvas; const Rect: TRect);
begin
  case FColorStyle of
    mycsGray:         DrawGray(ACanvas, Rect);
    mycsBlackAndWhite: DrawBlackAndWhite(ACanvas, Rect);
  else
    inherited Draw(ACanvas, Rect);
  end;
end;
Für die Anzeige genügt eine Paintbox, im OnPaint wird das aktuell ausgewählte Bild mit seinen Einstellungen gezeichnet.
Den Steuerelementen werden die zum Bild gehörenden Einstellungen zugewiesen.
Vereinfacht:
Delphi-Quellcode:
TMyForm = class(TForm)

  procedure DoOnTabChanged(Sender: TObject);
  procedure DoOnRadioClick(Sender: TObject);
  procedure DoOnPaintBoxPaint(Sender: TObject);
private
  FPictureList: TMyPictureList;
  function GetCurrentPicture: TMyPicture;
end;

procedure TMyForm.DoOnTabChanged(Sender: TObject);
var
  Picture: TMyPicture;
  ColorStyle: TMyColorStyle;
begin
  Picture := GetCurrentPicture;
  if Assigned(Picture) then
    ColorStyle := Picture.ColorStyle
  else
    ColorStyle := mycsDefault;

  RBtnDefault.Checked := ( ColorStyle = mycsDefault );
  RBtnGray.Checked   := ( ColorStyle = mycsGray );
  RBtnBW.Checked     := ( ColorStyle = mycsBlackAndWhite );
  {neu zeichnen}
  PaintBox.Invalidate;
end;

procedure TMyForm.DoOnRadioClick(Sender: TObject);
var
  Picture: TMyPicture;
  ColorStyle: TMyColorStyle;
begin
  ColorStyle := mycsDefault;
  Picture := GetCurrentPicture;
  if Assigned(Picture) then
  begin
    if     RBtnBW.Checked  then ColorStyle := mycsBlackAndWhite
    else if RBtnGray.Checked then ColorStyle := mycsGray;
  end;
  Picture.ColorStyle := ColorStyle;
  {neu zeichnen}
  PaintBox.Invalidate;
end;

procedure TMyForm.DoOnPaintBoxPaint(Sender: TObject);
begin
  PaintBox.Canvas.StretchDraw(PaintBox.ClientRect, GetCurrentPicture);
end;

function TMyForm.GetCurrentPicture: TMyPicture;
var
  idx: Integer;
begin
  idx := TabControl.TabIndex;
  if (idx >= 0) and (idx < FPictureList.Count) then
    Result := FPictureList[idx]
  else
    Result := nil;
end;

Schwedenbitter 15. Okt 2014 20:39

AW: TImages verwalten
 
Liste der Anhänge anzeigen (Anzahl: 1)
Zitat:

Zitat von Blup (Beitrag 1276043)
Mir ist nicht klar für was du überhaupt ein angedocktes Fenster benötigst.
Ich vermute du vermischt die Anzeige und das Halten der Daten.
...

Diese Vermutung wird richtig sein.
Ich finde aber die Forms nicht schlecht. Denn ich kann den Code für diese separat verwalten, was den Code später sogar besser warten lässt. Und ich möchte auch keine Querverweise haben. Das heißt, die Fenster wissen nichts voneinander und kümmern sich - bei Bedarf - um das Umwandeln selbst. Es verkompliziert es daher, eine Liste mit den Bilder im Hauptprogramm (=Code des MainForm) vorzuhalten und zu warten - mit Ausnahme der Liste für die Instanzen der Fenster.

Ich hatte bereits in meinem ersten Post eingestellt, wie ich es gern hätte. Genau so sieht es jetzt auch aus.
Der Zugriff auf die Bild-Daten klappt. Die Bilder sollen auch nicht in Grau angezeigt, sondern nur in Grau gespeichert werden. Ich habe dennoch noch einmal einen Screenshot angehängt.

Schwedenbitter 4. Nov 2014 13:13

AW: TImages verwalten
 
Ich bin jetzt (urlaubsbedingt) schon etwas weiter:
Mein DockForm bietet ein Ereignis an, welches vor dem Löschen ausgelöst wird und die Adresse des DockForms übergibt. Damit bekommt das Hauptfenster mit, das und was gelöscht wurde. Danke insoweit für Eure wertvollen Tipps!

Jetzt wollte ich das gern auf Speicherlecks untersuchen, bevor ich weitermache. Dazu habe ich mir FastMM4991 runtergeladen. Ich hatte FastMM schon vor Jahren benutzt. Meiner Erinnerung nach muss man es einfach als erste Unit in die dpr-Datei einbinden. Anderes Infos habe ich trotz Suche im Forum nicht gefunden.
Kann es sein, dass FastMM unter Delphi XE5 nicht mehr funktioniert?

Es kommt keine Meldung. Auch dann nicht, wenn ich in FormCreate einfach folgenden Code einfüge:
Delphi-Quellcode:
With tStringList.Create Do
Begin
  Append('Datenmüll');
End;
FastMM scheint schon lange nicht mehr gepflegt und explizit zu XE5 und FastMM konnte ich nichts finden.

Was mache ich falsch?
Falls FastMM nicht mehr funktioniert, womit erforscht man denn heutzutage Speicherlecks?

Gruß, Alex

Sir Rufo 4. Nov 2014 13:41

AW: TImages verwalten
 
Delphi-Quellcode:
FastMM
ist seit ??? in Delphi integriert, wird also schon direkt mitgeliefert.

Wenn du den Bericht über Speicherlecks am Ende haben möchtest, dann musst du das lediglich einschalten mit
Delphi-Quellcode:
ReportMemoryLeaksOnShutdown := true;
(am besten gleich am Anfang in der Projekt-Datei)

himitsu 4. Nov 2014 14:13

AW: TImages verwalten
 
Zitat:

Zitat von Sir Rufo (Beitrag 1278662)
Delphi-Quellcode:
FastMM
ist seit ??? in Delphi integriert,

Ich glaub D2006/TDE, also würde passen.

Das externe FastMM braucht man eigentlich nur, wenn man z.B. die erweiterten Debuggingfunktionen nutzen möchte. (Prüfen auf Pufferüberläufe, Schreiben nach Free, wer hat was wann erstellt usw.)


FastMM funktioniert noch und ja, es muß als Erstes in die DPR (außer man benutzt Packages, denn da muß es als Erstes in die DPK des ersten Packages)

Schwedenbitter 5. Nov 2014 09:33

AW: TImages verwalten
 
Danke!
Und da ich Delphi jetzt einfach mal so glaube, freue ich mich, dass mein Code bis hierhin keine Speicherlecks produziert hat.

Eine letzte Frage habe ich dann aber trotzdem noch:
Ich hatte hier (etwas weiter unten) bereits geschrieben, wie ich auf die einzelnen Forms im PageControl zugreife. Den Code habe ich um 2 Zeilen kürzen können. Ich würde es aber gern noch übersichtlicher gestalten und daher den Zugriff casten. Folgendes funktioniert leider nicht:
Delphi-Quellcode:
// Aus diesem hier ...
Procedure TMainForm.FormKeyDown(Sender: TObject; Var Key: Word;
   Shift: TShiftState);
Var
   lDockForm   : TDockForm;
Begin
   If (PCPics.PageCount = 0) Then Exit;
   If (Key = VK_DELETE) Then
   Begin
      lDockForm:=fPicList.Items[PCPics.ActivePageIndex];
      lDockForm.BtnDelete.Click;
   End;
End;

// ... soll auch an mehreren anderen Stellen so etwas hier werden:
Procedure TMainForm.FormKeyDown(Sender: TObject; Var Key: Word;
   Shift: TShiftState);
Begin
   If (PCPics.PageCount = 0) Then Exit;
   If (Key = VK_DELETE) Then
      With (fPicList.Items[PCPics.ActivePageIndex] As TDockForm) Do
         BtnDelete.Click;
      // geht auch nicht :-)
      (fPicList.Items[PCPics.ActivePageIndex] As TDockForm).BtnDelete.Click;
End;
Wieso geht es so nicht?
Kann man es irgendwie (anders) machen?

[edit]
Ich wäre nicht auf die Idee gekommen, dass es mind. 3 Varianten gibt, wie man casten kann. So klappt es:
Delphi-Quellcode:
Procedure TMainForm.FormKeyDown(Sender: TObject; Var Key: Word;
   Shift: TShiftState);
Begin
   If (PCPics.PageCount = 0) Then Exit;
   If (Key = VK_DELETE) Then
      TDockForm(fPicList.Items[PCPics.ActivePageIndex]).BtnDelete.Click;
End;
Aus 8 Zeilen 3 gemacht. Sehr schön.
[/edit]

Gruß, Alex

Jumpy 5. Nov 2014 10:40

AW: TImages verwalten
 
Kürzer heißt ja nicht unbedingt übersichtlicher/lesbarer, sprich ich finde den 8-Zeiler verständlicher, aber das ist ja bekanntlich Geschmacksache. Aber getreu dem Motto Wenn-Schon-Denn-Schon würde ich dann aber auch einen Zweizeiler draus machen:
Delphi-Quellcode:
If (Key = VK_DELETE) And (PCPics.PageCount > 0) Then
  TDockForm(fPicList.Items[PCPics.ActivePageIndex]).BtnDelete.Click;
So fällt auch das unschöne (ich weiß: auch weider Geschmacksache) vorzeitige exit weg.

mquadrat 5. Nov 2014 11:30

AW: TImages verwalten
 
ReportLeaksOnShutdown zeigt natürlich nur Speicherlecks, die beim Beenden der Anwendung noch bestehen. Ist also z.B. nicht geeignet um 24/7 Anwendungen Lecks zu finden, wenn Objekte mit dem Hauptform als Owner erstellt werden. (Soll es ja geben)

Schwedenbitter 5. Nov 2014 12:08

AW: TImages verwalten
 
Zitat:

Zitat von Jumpy (Beitrag 1278743)
Kürzer heißt ja nicht unbedingt übersichtlicher/lesbarer, sprich ich finde den 8-Zeiler verständlicher, aber das ist ja bekanntlich Geschmacksache. Aber getreu dem Motto Wenn-Schon-Denn-Schon würde ich dann aber auch einen Zweizeiler draus machen ...
So fällt auch das unschöne (ich weiß: auch weider Geschmacksache) vorzeitige exit weg.

Das stimmt. Es fragt sich bloß, was am Ende (durch mich) besser zu warten sein wird. Das weiß nur die Zeit.
Einen Zweizeiler kann ich nicht drauß machen, weil später andere Tastenabfragen hinzukommen werden. Ich fange gerade erst an. Ich mag ein solches Exit auch nicht wirklich aber es spart einen Tab-Einzug und macht den weiteren Code damit lesbarer.

Zitat:

Zitat von mquadrat (Beitrag 1278752)
ReportLeaksOnShutdown zeigt natürlich nur Speicherlecks, die beim Beenden der Anwendung noch bestehen. Ist also z.B. nicht geeignet um 24/7 Anwendungen Lecks zu finden, wenn Objekte mit dem Hauptform als Owner erstellt werden. (Soll es ja geben)

Danke für die Erläuterung. Das mit dem Hauptform als Owner gilt dann sicherlich auch für Objekte auf dem Hauptform (z.B. TPageControl) als Owner. Das würde ich natürlich auch gern testen.

Gibt es einen Schalter, mit dem man auch die bösen, unsichtbaren Lecks des Hauptforms herausfindet?


Alle Zeitangaben in WEZ +1. Es ist jetzt 19:30 Uhr.

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