Entwickler-Ecke

Grafische Benutzeroberflächen (VCL & FireMonkey) - ImageList - schnelle Anzeige


hRb - Mo 04.12.17 16:06
Titel: ImageList - schnelle Anzeige
Hallo Freunde,
ich möchte an Vorgängerfragen zur ImageList anknüpfen. Habe schon seit längerem in kleines Programm zur Datei-Anzeige und zum -Umbenennen; dargestellt in einem StringGrid. Dabei ist auch Verschieben nach oben/unten realisiert. Gerne hätte ich das Verschieben(Sortieren) auch optisch realisiert und will ein ImageList-Objekt zur Realisierung hinzufügen.
In einem Lehrbuch fand ich zu einem "Diabetrachter" folgende Befehlsfolge (über einem Image1-Objekt liegt ein Memo, das wechselseitig visible wird):

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:
procedure TForm1.FileListBox1Click(Sender: TObject);
var FileExt: string[4];
begin
  FileExt := UpperCase(ExtractFileExt(FileListBox1.Filename));
  if (FileExt='.BMP')or(FileExt='.ICO')or(FileExt='.WMF')or(FileExt='.JPG'then begin
    memo1.visible := false;
    try
        Image1.Picture.LoadFromFile(FileListBox1.Filename);
        if FileExt = '.JPG' then Image1.Picture.Graphic := Image2.Picture.Graphic else
        if FileExt = '.BMP' then Image1.Picture := Image1.Picture else
        if FileExt = '.ICO' then Icon := Image1.Picture.Icon else
        if FileExt = '.WMF' then Image1.Picture.Metafile := Image1.Picture.Metafile;
    except
          messagebox(0,'Grafik fehlerhaft','Problem',16);
    end;
  end;
  if (FileExt='.TXT')or(FileExt='.PAS')or(FileExt='.INI')or(FileExt='.BAT')or(FileExt='.CMD')or(FileExt='.BAS'then begin
    memo1.visible := True;
    try
        memo1.lines.clear;
        memo1.lines.LoadFromFile(FileListBox1.Filename);
    except
          messagebox(0,'Datei fehlerhaft','Problem',16);
    end;
  end;
end;

Ich habe dieses Beispiel verändert und zeige in drei Image-Objekten die aktive Datei, sowie noch Vorgänger und Nachfolger an. Es funktioniert, aber..
- das Verfahren ist viel zu langsam (jeweils LoadFromFile)
- beim Image-Objekt muss der Bildtyp (bmp, ico, jpg, etc) eingestellt werden, sonst ggf Laufzeitfehler. Da es x-Grafikformate gibt, müsste die ggf. alle abgefragt werden

Etwas neidisch schaue ich da auf die Geschwindigkeit beim Explorer, der alle Dateien anzeigt.
Muss ich statt Image- ein ImageList-Objekt verwenden und wenn ja:
Frage: Gibt es im Forum ein Codebeispiel wie man eine ImageList mit den Dateien eines Verzeichnisses füllt? - in Mimiaturansicht/Gr0ße Symbole?
oder kann man auf die tumb.db des Verzeichnisses zugreifen (wie)?


Narses - Mo 04.12.17 17:15

Moin!

user profile iconhRb hat folgendes geschrieben Zum zitierten Posting springen:
Etwas neidisch schaue ich da auf die Geschwindigkeit beim Explorer, der alle Dateien anzeigt.
Muss ich statt Image- ein ImageList-Objekt verwenden und wenn ja:
Frage: Gibt es im Forum ein Codebeispiel wie man eine ImageList mit den Dateien eines Verzeichnisses füllt? - in Mimiaturansicht/Gr0ße Symbole?
Bei der ImageList bin ich mir sicher, dass sie nicht für diesen Zweck „erfunden“ wurde, das darunter liegende native Windows-Objekt ist nämlich relativ eingeschränkt, was die Menge an Daten (=Images) angeht, die verwaltet werden können. Das Ding ist eher für kleine, statische Icons und sowas für die Nutzung in der Anwendung gedacht (auf Buttons, in Menüs, etc.).

user profile iconhRb hat folgendes geschrieben Zum zitierten Posting springen:
oder kann man auf die tumb.db des Verzeichnisses zugreifen (wie)?
Ohne Details zu kennen, wie man diesen System-Image-Cache nutzt - das ist der richtige Weg, um die Performance zu steigern: du musst die Daten cachen, anders geht das kaum. :nixweiss:

cu
Narses


jaenicke - Do 07.12.17 12:28

user profile iconNarses hat folgendes geschrieben Zum zitierten Posting springen:
Ohne Details zu kennen, wie man diesen System-Image-Cache nutzt
Zum Glück gibt es eine entsprechende Doku für die API. ;-)

Schon in Win2k gab es IExtractImage:
https://msdn.microsoft.com/en-us/library/windows/desktop/bb761848.aspx

Ich würde aber eher die ab Vista mit den ganzen neuen APIs verfügbare Thumbnail Cache API (via IThumbnailCache usw.) benutzen:
https://msdn.microsoft.com/en-us/library/windows/desktop/bb774628.aspx

Alternativ gibt es noch IShellItemImageFactory, aber da bekommst du nur ein HBITMAP während IThumbnailCache noch mehr Informationen zu dem Bild liefert:
https://msdn.microsoft.com/en-us/library/windows/desktop/bb761084.aspx


Delete - Do 07.12.17 18:13

- Nachträglich durch die Entwickler-Ecke gelöscht -


Sinspin - Do 07.12.17 19:39

Das wird sicher ein Interface sein dass Du bei Delphi aus einer typelib installieren kannst. Musst nur in der Windows-Doc schauen wo es sich versteckt.
Und user profile iconjaenicke war so nett schon so nett Dir die passenden Links zu liefern.


Delete - Do 07.12.17 22:18

- Nachträglich durch die Entwickler-Ecke gelöscht -


jaenicke - Fr 08.12.17 08:38

Ich hatte es gestern nicht mehr geschafft zu antworten, aber ich hatte es schon ausprobiert.

Die Übersetzung passt, ich würde allerdings out statt var benutzen. Außerdem fehlen dir noch die CLSIDs.
Und deine Enumerationen müssen Konstanten mit Integer als Zielwert sein, damit du diese auch kombinieren kannst.

Mein Vorschlag ist:

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:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
uses
  Winapi.ShlObj, Winapi.ActiveX;

const
  CLSID_LocalThumbnailCache: TGuid = '{50EF4544-AC9F-4A8E-B21B-8A26180DB13F}';
  CLSID_SharedBitmap: TGuid = '{4db26476-6787-4046-b836-e8412a9e8a27}';

  WTS_NONE = $00000000;
  WTS_EXTRACT = $00000000// Extract the thumbnail if it is not cached.
  WTS_INCACHEONLY = $00000001// Only return the thumbnail if it is cached.
  WTS_FASTEXTRACT = $00000002// If not cached, only extract the thumbnail if it is embedded in EXIF format, typically 160x120.
  WTS_FORCEEXTRACTION = $00000004// Ignore cache and extract thumbnail from source file.
  WTS_SLOWRECLAIM = $00000008;
  // Thumbnail has an extended lifetime. Use for volumes that might go offline, like non-fixed disks.
  WTS_EXTRACTDONOTCACHE = $00000020// Extract but do not add to cache.
  WTS_SCALETOREQUESTEDSIZE = $00000040;
  // Windows 7 and later. If necessary, shrink the bitmap (preserving aspect ratio) so width and height fit the given size.
  WTS_SKIPFASTEXTRACT = $00000080// Windows 7 and later. Do not attempt to extract the thumbnail embedded in EXIF format.
  WTS_EXTRACTINPROC = $00000100;
  // Windows 7 and later. Run the thumbnail extractor InProc. Use for debugging thumbnail extractors.
  WTS_CROPTOSQUARE = $00000200// Windows 8 and later. If necessary; crop the bitmap to a square.
  WTS_INSTANCESURROGATE = $00000400;
  // Windows 8 and later. Create a surrogate for this instance of the cache; rather than using the shared dllhost surrogate
  WTS_REQUIRESURROGATE = $00000800// Windows 8 and later. Require extractions to take place in the surrogate
  WTS_APPSTYLE = $00002000// Windows 8 and later. Pass the App-style flag to IThumbnailSettings if the provider supports it.
  WTS_WIDETHUMBNAILS = $00004000// Windows 8 and later. Stretch and crop the bitmap to a .7 aspect ratio.
  WTS_IDEALCACHESIZEONLY = $00008000;
  // Windows 8 and later. Return from the ideal cache snap size only. The returned image may still be larger but it will be pulled from the correct cache entry
  WTS_SCALEUP = $00010000// Windows 8 and later. If necessary, stretch the bitmap so width and height fit the given size.

  WTS_DEFAULT = 0;
  WTS_LOWQUALITY = 1;
  WTS_CACHED = 2;
  WTSAT_UNKNOWN = 0;
  WTSAT_RGB = 1;
  WTSAT_ARGB = 2;

type
  WTS_FLAGS = LongInt;
  WTS_CACHEFLAGS = LongInt;
  WTS_ALPHATYPE = LongInt;

  WTS_THUMBNAILID = record
    rgbKey: array [0 .. 15of Byte;
  end;

  // This interface is used as a thin wrapper around HBITMAP objects.  It allows an
  // HBITMAP to be ref-counted and protected from having its underlying data be changed
  ISharedBitmap = interface(IUnknown)
    ['{091162a4-bc96-411f-aae8-c5122cd03363}']
    function GetSharedBitmap(out phbm: HBITMAP): HRESULT; stdcall;
    function GetSize(out pSize: TSize): HRESULT; stdcall;
    function GetFormat(out pat: WTS_ALPHATYPE): HRESULT; stdcall;
    function InitializeBitmap(hbm: HBITMAP; wtsAT: WTS_ALPHATYPE): HRESULT; stdcall;
    function Detach(out phbm: HBITMAP): HRESULT; stdcall;
  end;

  // Interface for the system thumbnail cache.  GetThumbnail is used to extract and return
  // thumbnails for IShellItems.  The cache behavior can be specified using one or more of
  // the WTS_FLAGS.  GetThumbnailID allows the caller to retrieve an image known to be in
  // the cache via its ID (which was returned by GetThumbnail).
  IThumbnailCache = interface(IUnknown)
    ['{F676C15D-596A-4ce2-8234-33996F445DB1}']
    function GetThumbnail(pShellItem: IShellItem; cxyRequestedThumbSize: UINT; flags: WTS_FLAGS; out ppvThumb: ISharedBitmap;
      out pOutFlags: WTS_CACHEFLAGS; out pThumbnailID: WTS_THUMBNAILID): HRESULT; stdcall;
    function GetThumbnailByID(thumbnailID: WTS_THUMBNAILID; cxyRequestedThumbSize: UINT; out ppvThumb: ISharedBitmap;
      out pOutFlags: WTS_CACHEFLAGS): HRESULT; stdcall;
  end;

// ...

function GetThumbnail(const AFilename: stringconst ATarget: TBitmap; const ARequestedThumbSize: integer): HRESULT;
type
  TRGBTripleArray = array[Word] of TRGBTriple;
  pRGBTripleArray = ^TRGBTripleArray;
var
  ThumbnailCache: IThumbnailCache;
  ShellItem: IShellItem;
  Thumb: ISharedBitmap;
  OutFlags: WTS_CACHEFLAGS;
  ThumbnailID: WTS_THUMBNAILID;
  ThumbnailSize: TSize;
  hbm: HBITMAP;
  Col, Line: integer;
  SourceLine, TargetLine: pRGBTripleArray;
  Tmp: TRGBTriple;
begin
  Result := CoInitialize(nil);
  if Succeeded(Result) then
    try
      Result := CoCreateInstance(CLSID_LocalThumbnailCache, nil, CLSCTX_INPROC, IThumbnailCache, ThumbnailCache);
      if Succeeded(Result) then
      begin
        Result := SHCreateItemFromParsingName(PChar(AFilename), nil, IShellItem, ShellItem);
        if Succeeded(Result) then
        begin
          Result := ThumbnailCache.GetThumbnail(ShellItem, ARequestedThumbSize, WTS_EXTRACT or WTS_SCALETOREQUESTEDSIZE,
            Thumb, OutFlags, ThumbnailID);
          if Succeeded(Result) then
          begin
            Thumb.GetSize(ThumbnailSize);
            Result := Thumb.GetSharedBitmap(hbm);
            if Succeeded(Result) then
            begin
              ATarget.SetSize(ThumbnailSize.cx, ThumbnailSize.cy);
              ATarget.Handle := hbm;
              ATarget.Dormant;
              // Dormant --> make own DDB bitmap to be independant from DIB bitmap in handle (can be freed afterwards)

              // Now we need to flip the image vertically because it is provided upside down
              ATarget.PixelFormat := pf24bit;
              for Line := 0 to (ATarget.Height div 2) - 1 do
              begin
                SourceLine := ATarget.ScanLine[Line];
                TargetLine := ATarget.ScanLine[ATarget.Height - Line - 1];
                for Col := 0 to ATarget.Width - 1 do
                begin
                  Tmp := TargetLine[Col];
                  TargetLine[Col] := SourceLine[Col];
                  SourceLine[Col] := Tmp;
                end;
              end;
            end;
          end;
        end;
      end;
    finally
      CoUninitialize;
    end;
end;

procedure TForm37.Button1Click(Sender: TObject);
var
  Tmp: TBitmap;
begin
  Tmp := TBitmap.Create;
  try
    if Succeeded(GetThumbnail(Edit1.Text, Tmp, Image1.Width)) then
      Image1.Picture.Assign(Tmp);
  finally
    Tmp.Free;
  end;
end;
Die optionalen Parameter können durch out nicht als nil angegeben werden. Wenn du das möchtest, musst du statt var/out direkte Pointer benutzen.


Delete - Fr 08.12.17 15:40

- Nachträglich durch die Entwickler-Ecke gelöscht -


hRb - Sa 09.12.17 13:36

Beitrag mit Interesse verfolgt. Freue mich, eine Frage gestellt zu haben, die auch andere interessiert.
Als Laie scheinen mir die Ausführungen bei Microsoft allerdings recht kompliziert oder "unscharf" zu klingen. Insbesondere wenn ab Vista von einem Cache gesprochen wird, der über den Einzel-Ordner hinaus geht (IThumbnailCache). Wie grenzt man dann wieder auf Ordneransicht ein? Und wie "füttert" man eine ImageList mit einer in der Anzahl nahezu unbegrenzten BitMaps? Andererseits klingt es hoffnungsvoll, wenn es bei MS weiter heißt: "Ihre Anwendung muss keine Implementierung des Miniaturansichtsextraktors bereitstellen".
Verstehe ich Frühlingsrolle richtig, dass er ein lauffähiges Beispiel zur Verfügung stellen will? Wäre super!
Ich könnte dann meinerseits ein Umbenenn-Tool mit einer grafischen Sortierfunktion zur Verfügung stellen. Die zeigt derzeit aus Gründen der Geschwindigkeit nur das aktive angewählte Bild mit Vorgänger und Nachfolger und befriedigt daher nicht wirklich.
hRb


jaenicke - Sa 09.12.17 14:10

user profile iconhRb hat folgendes geschrieben Zum zitierten Posting springen:
Verstehe ich Frühlingsrolle richtig, dass er ein lauffähiges Beispiel zur Verfügung stellen will? Wäre super!
Naja... mein Beispiel ist bereits vollständig lauffähig... :gruebel:


Delete - Sa 09.12.17 18:13

- Nachträglich durch die Entwickler-Ecke gelöscht -


hRb - Mi 17.01.18 19:33

Hallo jaenicke,
ich würde das Thema gerne nochmals aufgreifen. Zunächst Danke für Dein Beispiel das ich in mein Programm eingebaut habe. Ich habe auch die Link-Texte gelesen, wenn auch nicht alles verstanden.

So bleiben eine Reihe von Fragen:
1. Es werden nur Thumbnails von Bilddateien angezeigt und nicht wie beim Explorer auch die Icon anderer Dateiendungen (exe, txt, pas etc). Liegt das an den von Dir eingestellten Parametern? Nur Feststellung - es stört mich nicht, denn meine Aufgabe lautet ja "Bilder Sortieren" und das geschieht ja nach optischen Merkmalen also Grafik-Typ.
2. Meine Frage ist eher: in welchen Container stellt man die Image-Objekte. Ich verwende derzeit eine Scrollbox und zwar zum Test fünf TImage (Größe 160x120 wie empfohlen). Für einen Ordner mit mehreren hundert Bildern müsste ich wohl dynamisch genau so viele Image-Objekte erzeugen, positionieren(left,top) und darstellen wie Anzahl Dateien. Ist dies richtig? Oder befüllt man immer nur soviele Images wie am Bildschirm darstellbar? (dann wird beim Scrollen der Vorgang wieder langsamer)
3. Bei meinem Test (5 Image) lassen sich diese nicht positionieren, bzw nicht mit der Maus verschieben. Derzeit stelle ich alle Dateien des Ordners in einem StringGrid dar und habe zum Verschieben zwei Button: AUF und AB. Die Image-Bilder schiebe ich dabei mit. Funktioniert, ist aber nicht sehr elegant, weil zügiges Verschieben nicht gegeben (immer nur ein Schritt).

Mir fehlt es derzeit an Ideen wie ich das "grafische Verschieben" lösen kann. Hilfe-Tips möglich? Danke im Voraus!

PS: noch eine Bemerkung an Frühlingsrolle. Bei mir sind die Ordner "Windows Kits" nicht vorhanden (Update-Opfer von WIN7 auf WIN10?)
Zitat:
C:\Program Files (x86)\Windows Kits\8.0\Include\um
C:\Program Files (x86)\Windows Kits\8.1\Include\um

In Original WIN10 finde ich mit leicht verändertem Pfad Ordner \um, aber der ist leer

Moderiert von user profile iconNarses: Beiträge zusammengefasst

Ich habe vergessen und muss nach Frage nachlegen:
mein Image ist - wie beschrieben 160x120. Gleich welches Format ein Bild hat, wird das Image voll ausgefüllt, d.h ein Bild in Hochformat wird völlig verzerrt? Wie anpassen?


Delete - Do 18.01.18 10:10

- Nachträglich durch die Entwickler-Ecke gelöscht -


hRb - Do 29.03.18 15:51

Hallo,
ich musste das Thema längere Zeit ruhen lassen. Hier kurz mein derzeitiger Programm-Stand:

Nach Auswahl eines Ordners stehen in einem Stringrid die Filenamen des Ordners und alle File-Parameter wie: Datum, Uhrzeit, Dateilänge, etc. (hübsch auseinander-gedröselt). In einem Panel daneben zum Test 5 TImage und 5 Label (sie zeigen die aktive Stringgridzeile mit Vorgänger und Nachfolger an). Die Images (Bitmap) werden mit der Routine GetThumbnail von jaenicke über den Filename gefüllt, den Dateiname schreibe ich verkürzt ins dazugehörige Label. So weit so gut.
Bevor ich nun hunderte Images (Bitmap) und Label aufwändig selbst auf einer Oberfläche plaziere (genau so viele wie Dateien im Ordner), empfiehlt Frühlingsrolle
Zitat:
Ich empfehle dir die Icons in einer TListView darzustellen.
Hier wird einem vermutlich viel Arbeit abgenommen. In der Delphi-Hilfe liest sich dies auch alles super (Bildchen klein/groß, als Liste, usw).
Mein Problem nun: wie befülle ich das TListview-Objekt mit meinen Daten? Ich finde dort Icons aber kein TImage-Variable. Wo/wie wird Verzeichnis bzw. Filename eingetragen? Auch kann ich nicht erkennen wie die "Optik" eines List-Feldes gestaltet werden kann/muss, bzw. dass im Label nur der Dateiname (ohne Ordner) steht.

Kann jemand helfen oder gibt es irgendwo ein Beispiel, damit etwas ähnliches wie ein Explorer dabei heraus kommt?
Danke und Ostergrüße von hRb


Delete - Fr 30.03.18 07:46

- Nachträglich durch die Entwickler-Ecke gelöscht -


hRb - Mi 11.04.18 20:56

Hallo, liebe Helfer
Der Wille ein Listview korrekt zu füllem ist da, in der Umsetzung gibt es aber immer noch die eine oder andere Schwierigkeit. Um die Probleme besser darzustellen, habe ich mein Programm auf die wesentlichen Funktionen reduziert und als zip-Datei beigefügt. Nach Programmstart und Auswahl eines Ordners sieht man:
- Links ein StringGrid mit den Dateidaten des Verzeichnisses (funktioniert)
- Mittig vier Image-Objekte (zum Test) um zu zeigen, dass die benutzten Funktionen korrekt arbeiten. Nach Klick auf eine StringGridzeile werden mit Hilfe der Proceduren StringGridSelectCell und ShowImage folgendes angezeigt: die ausgewählte Datei ( mit Vorgänger, aktiv ausgewählte Datei, zwei Nachfolger). Weiterhin unter Image.Hint die Datei-Parameter (funktioniert bei allen Dateiendungen).
- Rechts das Listview-Objekt. Das Füllen mit der Procedure ButtonFuelleListViewClick funktioniert nicht korrekt. Im Test zeigt sich:
1. jpg-Hochkantformat-Bilder oder auf Quadrat zugeschnittene Dateien werden nicht angezeigt
2. bmp-Dateien werden nicht angezeigt
3. gelegentlich fehlt letzte Datei
Nach Zuweisung der ImageList an Listview sind die Count-Zeiger nicht identisch, d.h. durch das Fehlen einzelner Dateien kommt das gesamte Zähl- und Benennungssytem außer Tritt = ist falsch.

Was ist der Grund für die Differenz bzw. was mache ich falsch?
PS: ich arbeite mit Delphi 10.1.Berlin
Danke für die Geduld und evtl. Hilfe
hRb


Delete - Do 12.04.18 00:12

- Nachträglich durch die Entwickler-Ecke gelöscht -


Holgerx - Do 12.04.18 10:52

Hmm..

Hab mir das mal angeschaut (nachdem ich ein kleines bischen umgearbeitet habe für D6 ;) (Namespaces und Austausch des TFileOpenDialogs) ):

Die Funktion GetThumbnail erzeugt ein 'proportunales' Thumbnail.
Ist das Ausgangsbild bereits kleiner wie die MaxGröße, dann wird es in der originalen Größe zurück gegeben.

Wenn das resultierende Bitmap nur eine eine Größe (Höhe oder Breite) 'kleiner' hat, wie die Image-Größe in der ImageList gibt es (zumindestens in D6) eine Exception 'Ungültige Bildgröße' und es wird der ImageList nicht hinzugefügt.

Ich habe die MaxSize für das Thumnal mal auf 200 gesetzt und somit Bitmaps erzeugt, welche größer sind, wie in der ImageList. Hier wurden diese dann zwar hinzugefügt, jedoch beschnitten.

Um somit ein propotionales, eingepasstes Thumbnail zu bekommen, muss Du dieses zunächst auf ein für die ImageList passendes Bild malen und dann hinzufügen.

Hier mal eine funktionierendes Beispiel:
Hinweis:
Das eventuell doppelte Erstellen eines Thumbnail ist deshalb notwendig, da wir die Proportionen der Image-Datei erst bekommen, wenn das erste Thumbnail erstellt wurde.
Den Rahmen habe ich zum Testen um jedes Bild gezeichnet, kann also auch weggelassen werden.

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:
function AddThumb(AFileName : string; AImageList : TImageList):boolean;
var
  tmpThumb : TBitmap;
  tmpImage : TBitmap;
  x,y : integer;
  f : integer;
begin
  Result := false;
  tmpImage := TBitmap.Create;
  try
    tmpImage.Width := AImageList.Width;
    tmpImage.Height := AImageList.Height;

    tmpImage.Canvas.Rectangle(tmpImage.Canvas.ClipRect);

    tmpThumb := TBitmap.Create;
    try
      // Erstmal mit der Breite, um die Proportionen des Bildes zu bekommen
      if Succeeded(GetThumbnail(AFileName, tmpThumb, AImageList.Width)) then begin

        // Wenn Thumb nun zu Hoch..
        if tmpThumb.Height > tmpImage.Height then begin
          f := Trunc((tmpThumb.Width / tmpThumb.Height) * AImageList.Height);
          GetThumbnail(AFileName, tmpThumb, max(f,AImageList.Height));
        end;

        // Zentriert auf dem Sollimage Zeichnen
        x := (tmpImage.Width - tmpThumb.Width) div 2;
        y := (tmpImage.Height - tmpThumb.Height) div 2;
        tmpImage.Canvas.Draw(x,y,tmpThumb);

        try
          // Zur ImageList hinzufügen
          AImageList.Add(tmpImage,nil);
          Result := True;
        except
        end;
      end;
    finally
      tmpThumb.Free;
    end;
  finally
    tmpImage.Free;
  end;
end;


Deine Stelle würde dann nur noch so aussehen:


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:
procedure TForm1.ButtonFuelleListViewClick(Sender: TObject);
var anz, i: integer;
    item  : TListItem;
    s     : string;
    ico   : TIcon;
begin
  Screen.Cursor := crHourGlass;
  ImageList1.Clear;
  Listview1.Clear;
  anz:= StringGrid1.RowCount-1{-FixRow} ;
  // alle Bilder in ImageList einfügen
  for i:=1 to anz do
  begin
    s := LabelPfad.Caption + StringGrid1.Cells[1,i];
    if not AddThumb(s, ImageList1) then
    begin // Icon der Nicht-Grafik-Datei  nach Frühlingsrolle
      ico := TIcon.Create;
      try
        GetFileIcon(s, ico); // gibt das Icon der momentanen Anwendung
        ImageList1.AddIcon(ico);
      finally
        ico.Free;
      end;
    end;
  end;

  ListView1.LargeImages := ImageList1;  // ImageList zuweisen
  ..



Getestet unter D6 mit GIF/BMP/JPG unterschiedlicher Größen und Proportionen..


hRb - Do 12.04.18 12:17

Hallo Holgerx,
klingt plausibel. Kannst Du bitte noch mitteilen was die Function max( ) in GetThumbnail leistet?
Zitat:
GetThumbnail(AFileName, tmpThumb, max(f,AImageList.Height));

zunächst Danke. hRb


jaenicke - Do 12.04.18 12:20

user profile iconhRb hat folgendes geschrieben Zum zitierten Posting springen:
Kannst Du bitte noch mitteilen was die Function max( ) in GetThumbnail leistet?
Siehe Doku:
http://docwiki.embarcadero.com/Libraries/Tokyo/de/System.Math.Max
Die Funktion gibt den größeren Wert zurück, entweder f oder AImageList.Height, je nachdem welcher größer ist.


Delete - Do 12.04.18 14:54

- Nachträglich durch die Entwickler-Ecke gelöscht -


hRb - Do 12.04.18 15:00

Danke jaenicke, Funktion Max war mir unbekannt. Nach Einfügen der Uses Math funktioniert mein Programm mit den Änderungen von Holgerx bestens. Es werden nun alle Dateien angezeigt. Zumindest hatte ich auf meinem Rechner bisher noch keinen Ordner bei dem Fehler aufgetreten sind.
Allerdings - und dies bitte nicht als Kritik - durch das zweimalige Laden ist bei großen Verzeichnissen der Geschwindigkeitsverlust spürbar - zumindest im Vergleich zum Dateimanager. Es gibt also "effizientere" Methoden.

Nachtrag:
Noch ein Letztes, bevor ich dieses Thema schließe. In meinem Beispiel steht etwas versteckt noch eine zweite unbeantwortete Frage:
Während ich mit Image.Hint die Dateiparameter anzeigen kann, fehlt mir noch die Lösung beim TlistItem; dort gibt es kein Hint. Ich habe schon einiges probiert, z.B
item.SubItems.Add('Text'); Aber der Text erscheint nicht beim Zeigen aufs Item. Welche schreibweise erzeugt einen "Hint-Effekt"?
Möchte mich dieser Stelle nochmals ganz herzlich für die großartige Unterstützung bedanken !!!
MfG hRb


Delete - Do 12.04.18 16:41

- Nachträglich durch die Entwickler-Ecke gelöscht -


Holgerx - Fr 13.04.18 09:03

Hmm..

user profile iconhRb hat folgendes geschrieben Zum zitierten Posting springen:

Allerdings - und dies bitte nicht als Kritik - durch das zweimalige Laden ist bei großen Verzeichnissen der Geschwindigkeitsverlust spürbar - zumindest im Vergleich zum Dateimanager. Es gibt also "effizientere" Methoden.


Alles kann 'Optimiert' werden!

Jedoch haben wir anhand des Dateinamens keine Proportionen (Größe) des Bildes, um vorab zu berechnen, ob wir die Höhe oder die Breite der ImageList als Max für GetThumbnail brauchen..

Der Zweite Aufruf von GetThumbnail wird ja auch nur gemacht, wenn das Thumbnail zu hoch sein würde bei Generierung mit der Breite der ImageList...

Dies ließe sich nur optimieren, indem Du vor GetThumbNail die Größe des Bildes ermittelst, jedoch ist es hier dann meist notwendig das gesamte Bild einzulesen und auch der entsprechenden Graphic dann die Höhe und breite auszulesen.
Alternativ kannst Du natürlich eine optimierte Einlese-Routine für die 'verschiedenen' Headers der Images schreiben, um so nur immer den Anfang einzulesen. Dies jedoch dann für jedes Grafik-Format separat (BMP/JPG/PNG/GIF....).

Eine weitere Option würde sein, dass Du 'immer' ein zu großes ThumbNail generieren läst und dieses dann mit StrechDraw nochmals auf die maximale Einpassgröße runter reduzierst. Was nun wirklich schneller ist, k.A.


hRb - Fr 13.04.18 19:55

Hallo Frühlingsrolle,
Dein Vorschlag Listview.Hint zu benutzen ist eine Option. Allerdings ist hierzu ein Maus-Klick erforderlich, erfüllt also nicht ganz exakt die Hint-Funktion. Wenn die einzelnen Item kein Hint haben, müsste man wohl die Mausbewegung auszuwerten. Dies ist im Moment aber nicht mein Hauptziel.
Wer die Diskussion hier verfolgt dem sei gesagt: Auf der Basis Deines Vorschlags und der folgenden kleinen Abänderung erscheinen Dateigröße und Dateidatum als Hint-Info.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
procedure TForm1.ListView1Click(Sender: TObject);
var i:integer;
begin
 if ListView1.ItemFocused <> nil then
//    ListView1.Hint := ListView1.ItemFocused.Caption;
  with StringGrid1 do begin
   i:= ListView1.ItemIndex + 1;
   ListView1.Hint:=
     Cells[1,i] +#10 + 'Byte=' +    //Dateiname +
     Cells[2,i] +#10 + Cells[3,i];  //Dateigröße + Datum
  end;
end;

Zum Thema Optimierung gibt es sicherlich Potential. Im Hinblick auf die erwähnten Create und Free in der Schleife der Procedure ButtonFuelleListViewClick gebe ich zu, dass ich die empfohlenen Anweisungen einfach in eine Schleife setzte. Andererseits hatte ich vor den Änderungen von Holgerx - als ich Create einmalig vor der Schleife aufrief - permanent Laufzeit-Error. Allerdings habe ich den Laufzeitfehler nicht nachhaltig untersucht. In meiner neuesten "optimierten" Version (Create nur einmal) tritt der Fehler nicht mehr auf. Werde dies beobachten!
Ansonsten bitte ich meinen Einwand zur Optimierung nicht zu ernst zu nehmen. Ich habe einen Ordner mit ca. 300 Bildern mehrfach aufgerufen und durchaus akzeptable Zeiten erreicht. Vielleicht lief beim ersten Test noch etwas im Hintergrund.

Was mir nach dem ersten Hurra allerdings noch auffällt ist, dass z.B. die Icons von Nicht-Grafikdateien in der Listview-Darstellung aufgebläht wird auf 160 x 120 Pixel, während es in der Image-Darstellung in Originalgröße (klein) bleibt. Werde mir die AddThumb-Funktion nochmal genauer ansehen, ob dies anzupassen geht.
Zunächst möchte ich aber mit Vorrang mein Hauptziel weiter verfolgen - die optische Sortierung und danach Umbennnennung von Dateien. Vielleicht kann ich demnächst ein kleines Tool vorstellen.
Gruß hRb


Holgerx - Sa 14.04.18 11:44

Hmm..

user profile iconhRb hat folgendes geschrieben Zum zitierten Posting springen:

Was mir nach dem ersten Hurra allerdings noch auffällt ist, dass z.B. die Icons von Nicht-Grafikdateien in der Listview-Darstellung aufgebläht wird auf 160 x 120 Pixel, während es in der Image-Darstellung in Originalgröße (klein) bleibt. Werde mir die AddThumb-Funktion nochmal genauer ansehen, ob dies anzupassen geht.


Bedenke bitte, das die Icons nicht von AddThumb erstellt werden, sondern direkt, wenn AddThumb scheitert.

Zitat:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
    if not AddThumb(s, ImageList1) then
    begin // Icon der Nicht-Grafik-Datei  nach Frühlingsrolle
      ico := TIcon.Create;
      try
        GetFileIcon(s, ico); // gibt das Icon der momentanen Anwendung
        ImageList1.AddIcon(ico);
      finally
        ico.Free;
      end;
    end;


Wenn Du hier, wie in AddThumb, das Icon auf ein Zwischen-Bitmap malen würdest, statt direkt mit AddIcon zuzuweisen, dann würde das Icon auch die Original-Größe haben, jedoch bei 32x32 Pixel auf einem 160x120 Pixel Bitmap würde dies recht klein sein.


hRb - Fr 15.06.18 22:52

Hallo Entwickler-Gemeinde,
Meine Frage ist ja schon seit geraumer Zeit beantwortet. Höchste Zeit das Thema zu schließen (werde es morgen tun). Ich habe solange gewartet,
weil Frühlingsrolle schrieb:
Zitat:
Jetzt fehlt nur noch ein funktionstüchtiges Anwendungsbeispiel. :gruebel:

Also habe ich getüfftelt, es liegen lassen und immer wieder verbessert. Als Anhang kann ich heute eine lauffähige Anwendung zum Sortieren und Umbenennen von Dateien beifügen. Selbstverständlich habe ich noch eine Menge Fragen zu Listview, die zu Verbesserungen führen könnten. Diese will ich wegen der Länge dieses Beitrages und zur Trennung der Probleme jedoch in getrennten Fragen stellen. Werde mich aber in den Zusatzfragen auf diese Anwendung beziehen. Vielleicht kann/will jemand das Programm selbst nutzen bevor man eine Profiprogramm kauft. (siehe Anlage).
Allen Helfern nochmals Danke. hRb


jaenicke - Sa 16.06.18 08:35

Dort könntest du übrigens gut die Ribbonbar einsetzen. Dann sähe es etwas moderner aus und du könntest die verschiedenen Möglichkeiten gruppiert unterbringen, wenn es später mehr werden.
Aber es ist schon gut gemacht, auch wenn es noch nicht viele Möglichkeiten bietet.

Was mir beim Umbenennen von Dateien fehlt sind Regular Expressions. Ohne die hilft mir solch ein Programm leider nichts. Denn damit kann ich den Dateinamen nach meinen Regeln auseinander nehmen und anders wieder zusammensetzen.

Und statt nur ganze Ordner auswählen zu können würde ich auch einfach einzelne Dateien drauf ziehen lassen.

Gut finde ich, dass du auch eine Vorschau drin hast.

user profile iconhRb hat folgendes geschrieben Zum zitierten Posting springen:
Vielleicht kann/will jemand das Programm selbst nutzen bevor man eine Profiprogramm kauft. (siehe Anlage).
Kaufen muss man das nicht, das gibt es für private Zwecke auch kostenlos (mit Bitte um Spenden):
https://www.advancedrenamer.com
Das benutze ich mit Regular Expressions dann z.B. (als ganz einfaches Beispiel) so, diese Möglichkeit würde ich auf jeden Fall einbauen:

AdvancedRenamer


hRb - Sa 16.06.18 14:07

Hallo Jaenicke,
so ganz habe ich Deinen Hinweis mit den "Regular Expressions" noch nicht verstanden (Begriff noch nie gehört). Mit meiner Formel
[N6-8][N1-5][N9-30] oder [C] [N1-5][N9-30] (beachte Blank als Textzeichen dazwischen) erreiche ich das gleiche Ergebnis wie bei Deinem Beispiel. Dabei erscheint mir meine Formel etwas verständlicher. Ist allerdings nur der erste Eindruck. Muss mir das genannte Programm mal laden und genauer ansehen. (Hab ich inzwischen - jedoch nicht so genau)
Allerdings, eines sehe ich schon jetzt: Mein Hauptziel liegt nicht im Umbenennen. Ich will zuerst sortieren (Bilder) und da brauche ich für jede Datei das Vorschaubild. Das kommt beim Advanced Renamer etwas kurz. Ich werde mir aber überlegen die Ansichten komplett umzuschalten (von StringGrid auf Listview) Dann habe ich mehr Fläche zum Verschieben der Bilder und auch einige andere Probleme (siehe neue Zusatzfragen) weniger. Aber danke für Tipp.
Gruß hRb


jaenicke - Sa 16.06.18 17:38

Stell dir einfach vor du hättest folgende Dateinamen:
Test 02 - blub.txt
Einerlei 04 - auweia.txt
Obstsalatschale 08 - nix.txt

Mit der Regular Expression funktioniert das ganz genauso wie das erste Beispiel. Dein Ausdruck wird damit vermutlich aber nicht mehr funktionieren.


hRb - So 17.06.18 14:29

Hallo jaenicke, Du hast mich überzeugt. Ich hoffe in Deinem Verweis auch einen PascalCode zu finden. Gruß hRb


jaenicke - So 17.06.18 16:34

In aktuellen Delphiversionen gibt es dafür TRegEx [http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.RegularExpressions.TRegEx].


Sinspin - Di 19.06.18 17:11

Ich will dich auf keinen Fall davon abhalten selber, was vieleicht viel besseres, zu entwickeln, aber als weitere Inspiration schlage ich mal Irfan View [https://www.irfanview.com/] vor.
Du kannst Bilder, umbenenen, konvertieren, sortieren, ...
Ich bin Hobbyfotograf, über die Jahre ist einiges zusammengekommen was sortiert werden musste, ich kann mir kein Leben ohne Irfan View vorstellen. :P