Virtual file list

I have a list of files (which do not necessarily exist) and I need to display it in a listview with accosiated icons, kind of like WinRar, WinZip or CuteFTP.

What is the most elegant way of doing this?
LVL 1
duke_nAsked:
Who is Participating?

Improve company productivity with a Business Account.Sign Up

x
 
MadshiConnect With a Mentor Commented:
Hmmm... Well, finally I tested it myself. The problem is that the stuff with the system image list doesn't seem to work for non-existing files. But you can you the following stuff, it works fine. It asks for an icon handle instead of an icon index and doesn't use the system image list, but a self created:

uses ShellAPI, CommCtrl;

procedure TForm1.FormCreate(Sender: TObject);
var sfi : SHFileInfo;
begin
  ListView1.SmallImages := TImageList.Create(self);
  ListView1.SmallImages.Width := 16;
  ListView1.SmallImages.Height := 16;
  ListView1.ViewStyle := vsReport;
  ListView1.Columns.Add.Width := 200;
  with ListView1.Items.Add do begin
    Caption := 'invalid.bmp';
    SHGetFileInfo('invalid.bmp', FILE_ATTRIBUTE_NORMAL, sfi, sizeOf(sfi), SHGFI_SYSICONINDEX or SHGFI_SMALLICON or SHGFI_USEFILEATTRIBUTES or SHGFI_ICON);
    ImageIndex := ImageList_AddIcon(ListView1.SmallImages.Handle, sfi.hIcon);
    DestroyIcon(sfi.hIcon);
  end;
end;

Regards, Madshi.

P.S: "Virtual" means, that the single items are not added by using "ListView1.Items.Add", but instead "ListView1.Items.Count" is set, the item data is asked from you by callback methods thereafter. This virtual mode is MUCH faster than adding the items manually. It's like day and night...
0
 
CesarioCommented:
Hi duke,

Here's some code from a recent project. FileList is a TListView. ScanDir() is a function from our product - it's basically a procedure that fills a TStrings object with a list of files matching a mask. You can ignore the TDirInfo(Node.Data) stuff - it's a small class that holds info on each folder as displayed in a TTreeView.TTreeNode.

This routine builds a TListView that's pretty much like the right pane in Windows Explorer, in that it supports both the list and report view, and displays the file type, size, and modified date in columns in report view.



{Gets files in a folder and displays them in the ListView}
procedure TExplorer.GetFilesInFolder(Node: TTreeNode);
var
  SL: TStringList;
  i: Integer;
  Dat: TDirInfo;
  AllSel: Boolean;
  NewItem: TListItem;
  FI: TSHFileInfo;
  Dt: TDateTime;
  TypeDesc: String;
begin
  if not Assigned(Node) then
    Exit;
  SL := TStringList.Create;
  if Screen.Cursor <> crHourglass then
    Screen.Cursor := crHourglass;
  try
    {Need easier access to Node.Data than TDirInfo(Node.Data) typecasts}
    Grab a local reference to the pointer}
    Dat := TDirInfo(Node.Data);
    {Get files in this folder, but don't include subfolder files}
    ScanDir(Dat.FullPath, '*.*', SL, False);
    SL.Sorted := True;
    {See if this folder has already been fully selected. If so, we don't need to add it
    to the Folders list or increment selection count or bytes}
    AllSel := (Folders.IndexOf(Dat.FullPath) > - 1) or (Dat.Status = dsFull);
    {Remove stuff that was previously displayed}
    FileList.Items.BeginUpdate;
    FileList.Items.Clear;
    {Is this an empty folder?}
    if SL.Count = 0 then
    begin
      FileList.SmallImages := StateImages;
      NewItem := FileList.Items.Add;
      NewItem.Caption := ' No files ';
      NewItem.ImageIndex := NoFilesIndex;
      FileList.Enabled := False;
      Exit;
    end;
    FileList.SmallImages := SysImages;
    {We have files. Add each one to the ListView}
    for i := 0 to SL.Count - 1 do
    begin
      {Create a new TListItem}
      NewItem := FileList.Items.Add;
      {Assign the filename portion}
      NewItem.Caption := ExtractFileName(SL[i]);
      FillChar(FI, SizeOf(TSHFileInfo), #0);
      {Get the icon for display, as well as the file type, with one function call. Note the flags:
      SHGFI_SMALLICON - we want the small icon
      SHGFI_SYSICONINDEX - we want the index into the system imagelist
      SHGFI_TYPENAME - we want the file type description if there is one}
      SHGetFileInfo(PChar(SL[i]), 0, FI, SizeOf(FI), SHGFI_ICON or SHGFI_SMALLICON
                                 or SHGFI_SYSICONINDEX or SHGFI_TYPENAME);
      {The subitems are only displayed in the 'detail' view, but they have to be there all the time.
      See if Windows knows what type file this is}
      TypeDesc := FI.szTypeName;
      if TypeDesc = '' then
        {Windows doesn't know - handle like Explorer does}
        TypeDesc := Upper(ExtractFileExt(SL[i])) + ' file';
      {Delete the period if we need to}
      if Length(TypeDesc) > 1 then
      begin
        if TypeDesc[1] = '.' then
          Delete(TypeDesc, 1, 1);
      end;
      {Display the file type description}
      NewItem.SubItems.Add(TypeDesc);
      {Here's the 'Size' column ...}
      NewItem.SubItems.Add(Comma([GetFileSize(SL[i])], False));
      {Assign the system imagelist index to this item}
      NewItem.ImageIndex := FI.iIcon;
      {Grab the file's time and date stamp and convert to Delphi TDateTime}
      Dt := FileDateToDateTime(FileAge(SL[i]));
      {Add the date column}
      NewItem.SubItems.Add(DateToStr(Dt));
      {Add the time column}
      NewItem.SubItems.Add(FormatDateTime('hh:nn:ss ampm', Dt));
      {If folder was fully selected, or this file was selected in a previous visit to this folder, check it}
      if AllSel or (Files.IndexOf(SL[i]) > - 1) then
        NewItem.Checked := True;
    end;
    FileList.Enabled := True;
  finally
    SL.Free;
    FileList.Items.EndUpdate;
    if Screen.Cursor <> crDefault then
      Screen.Cursor := crDefault;
  end;
end;



Best Regards

Cesario
0
 
duke_nAuthor Commented:
Wait a sec.
Did you read what I wrote???

I have a list of NON-EXISTING files and need to get their icons.

and I know all about SHGetFileInfo - I've been trying to make it work elegantly for a long time, but I see no way.
0
Free Tool: Port Scanner

Check which ports are open to the outside world. Helps make sure that your firewall rules are working as intended.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

 
darkloserCommented:
Get GXExperts :))
Thats c00l and free :))

regards
0
 
DragonSlayerCommented:
umm... non-existing files... so where to get their icons from???
0
 
MadshiCommented:
SHGetFileInfo like being used from Cesario shows even the icons for non-existing files. Of course it can't show the exe specific icon, it can only show the standard icon like the typical bmp icon for bitmaps and the dos exe icon for exes and so on. But what do you expect if the files don't exist? That's all you can get.

Except if you have the icons stored anywhere. Then it's a different thing.

Regards, Madshi.
0
 
DragonSlayerCommented:
Yup... agree with Madshi.
0
 
duke_nAuthor Commented:
to darkloser: what have gexperts to do with this(especially that I already have'em) ????????

To Dragonslayer: The icons are taken from file associations (that are stored in registry). You didn't actually believe that every .txt file icon is stored in it...? However, as Madshi mentioned, .exe file icons ARE stored inside each one, and we are going to have to treat them as iconless exe files (as WinRar, Winzip and CuteFTP do, for example).


and finally, I still can't make this work.
here is what I do:

FileListView.Items.BeginUpdate;
FileListView.Items.Clear;
SystemIL.Handle:=SHGetFileInfo('', 0, FI, SizeOf(FI), SHGFI_ICON or SHGFI_SMALLICON
                                or SHGFI_SYSICONINDEX or SHGFI_TYPENAME);
If (Data<>nil)and(Data.FileList<>nil) then
  For i:=0 to Data.FileList.Count-1 do
    begin
      FileData:=Data.FileList.Items[i];
      With FileListView.Items.Add do
       begin
          Caption:=FileData.FileName;
          Case  FileData.FileSize  of
          0..1024:SubItems.Add(IntToStr(FileData.FileSize)+'  B');
          1025..943718:SubItems.Add(IntToStr(FileData.FileSize div 1024)+' KB');
          else
            SubItems.Add(FloatToStr(FileData.FileSize/1048576)+' MB');
            tmp:=Pos('.',Subitems[0]);
            if Tmp<>0 then
              Subitems[0]:=Copy(Subitems[0],1,tmp+2)+' MB';
          end;
          SubItems.Add(DateToStr(FileData.FileDate));
          //ImageIndex
          FillChar(FI, SizeOf(TSHFileInfo), #0);
          SHGetFileInfo(PChar(FileData.FileName), 0, FI, SizeOf(FI), SHGFI_ICON or SHGFI_SMALLICON
                                  or SHGFI_SYSICONINDEX);
          ImageIndex := FI.iIcon;
        end;
    end;
FileListView.Items.EndUpdate;



hehe, this is very funny. on the first run, it just showed unknown file type icon for ALL the files. on second run it totally f*cked the system imagelist(For explorer and IE)
0
 
MadshiCommented:
Please give in the whole path, not only the file name. Then you'll get correct icons. About f*cking the system imagelist: Please set SystemIL.ShareImages to true.

Regards, Madshi.
0
 
MadshiCommented:
P.S: I've looked in my sources. I'm only using "SHGFI_SYSICONINDEX or SHGFI_SMALLICON", I'm not giving in "SHGFI_ICON". Perhaps that makes a difference?
0
 
duke_nAuthor Commented:
Oh, I totally forgot shareimages :))

do you think that if I give the full non-existing path rather than just a non-existing file name, it will work? I'll try.

and about SHGFI_ICON - I tried them all in every possible variations, and it doesn't seem to make a difference
0
 
MadshiCommented:
Which Delphi version do you have? In my D5 there's a demo called "Virtual ListView". It has all you need...
0
 
duke_nAuthor Commented:
It is showing the "unknown file type" icons for every file.



my D5 has that demo too but (frankly I do not know why it is called virtual) it shows a usual Explorer file list for existing files(probably using the same SHGetFileInfo).
0
 
duke_nAuthor Commented:
ahh, I see(I think).

So what I was talking about is not virtual at all? Is there a way to make it so?
0
 
duke_nAuthor Commented:
Oh, this works greatly.

But what about the memory?

here is what I do:

procedure TMainForm.FormCreate(Sender: TObject);
begin
  FileListView.SmallImages:=TImageList.Create(Self);
  FileListView.SmallImages.Width:=16;
  FileListView.SmallImages.Height:=16;
  FileListView.LargeImages:=TImageList.Create(Self);
  FileListView.LargeImages.Width:=32;
  FileListView.LargeImages.Height:=32;
end;




and then(on_nonexisting_dir_change :) )

 FileListView.SmallImages.Clear;
 FileListView.LargeImages.Clear;

 FileListView.Items.BeginUpdate;
 FileListView.Items.Clear;
.......


If (Data<>nil)and(Data.FileList<>nil) then
  For i:=0 to Data.FileList.Count-1 do
    begin
      FileData:=Data.FileList.Items[i];
      With FileListView.Items.Add do
       begin
          Caption:=ExtractFileName(FileData.FileName);
          Case  FileData.FileSize  of
          0..1024:SubItems.Add(IntToStr(FileData.FileSize)+'  B');
          1025..943718:SubItems.Add(IntToStr(FileData.FileSize div 1024)+' KB');
          else
            SubItems.Add(FloatToStr(FileData.FileSize/1048576)+' MB');
            tmp:=Pos('.',Subitems[0]);
            if Tmp<>0 then
              Subitems[0]:=Copy(Subitems[0],1,tmp+2)+' MB';
          end;
          SubItems.Add(DateToStr(FileData.FileDate));
          //ImageIndex
          if FileListView.ViewStyle=vsIcon then
            begin
              SHGetFileInfo(PChar(FileData.FileName), FILE_ATTRIBUTE_NORMAL, sfi, sizeOf(sfi), SHGFI_SYSICONINDEX
                                      or SHGFI_USEFILEATTRIBUTES or SHGFI_ICON);
              ImageIndex := ImageList_AddIcon(FileListView.LargeImages.Handle, sfi.hIcon);
            end
          else
            begin
              SHGetFileInfo(PChar(FileData.FileName), FILE_ATTRIBUTE_NORMAL, sfi, sizeOf(sfi), SHGFI_SYSICONINDEX or SHGFI_SMALLICON
                                      or SHGFI_USEFILEATTRIBUTES or SHGFI_ICON);
              ImageIndex := ImageList_AddIcon(FileListView.SmallImages.Handle, sfi.hIcon);
            end;
          DestroyIcon(sfi.hIcon);
        end;
    end;



Are we not leaking? should we free maybe the imagelists(on close) or is the form handling that?
0
 
MadshiCommented:
Well, to be sure you can free the imagelists yourself, but since we gave in "self" as the owner, I think the imagelists should be freed automatically. Not 100% sure, though...

>> So what I was talking about is not virtual at all?

Your code does not use the virtual mode of the list view.

>> Is there a way to make it so?

Look at the TListView.OwnerData property. It's all explained there. It's not so difficult. However, it is only worth the work, if the item count is quite large (e.g. > 100 or something like that). If you only deal with low item counts, you can forget about virtual mode. Then the Items.Add logic is fast enough...

Regards, Madshi.
0
 
duke_nAuthor Commented:
very well.

Thank you very much
0
 
duke_nAuthor Commented:
Madshi,
WOW this is a great thingy(I finally got some time and converted the file list to the virtual mode).

The list load now has a blazing speed! I mean, what took 15 seconds to load earlier, takes less than half a sec now.

The scroll is a bit funky, though(slower and stuff). Are you sure that the OnData gets called  only once for each item?

Anyway, thanks man(You are not a woman, are you? :)) ). This is great stuff.
(I hope ee fixed the notification problem and you get one about this comment)
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.