Solved

Virtual file list

Posted on 2001-06-26
18
331 Views
Last Modified: 2013-11-18
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?
0
Comment
Question by:duke_n
  • 8
  • 6
  • 2
  • +2
18 Comments
 
LVL 8

Expert Comment

by:Cesario
Comment Utility
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
 
LVL 1

Author Comment

by:duke_n
Comment Utility
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
 
LVL 1

Expert Comment

by:darkloser
Comment Utility
Get GXExperts :))
Thats c00l and free :))

regards
0
 
LVL 14

Expert Comment

by:DragonSlayer
Comment Utility
umm... non-existing files... so where to get their icons from???
0
 
LVL 20

Expert Comment

by:Madshi
Comment Utility
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
 
LVL 14

Expert Comment

by:DragonSlayer
Comment Utility
Yup... agree with Madshi.
0
 
LVL 1

Author Comment

by:duke_n
Comment Utility
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
 
LVL 20

Expert Comment

by:Madshi
Comment Utility
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
 
LVL 20

Expert Comment

by:Madshi
Comment Utility
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
IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

 
LVL 1

Author Comment

by:duke_n
Comment Utility
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
 
LVL 20

Expert Comment

by:Madshi
Comment Utility
Which Delphi version do you have? In my D5 there's a demo called "Virtual ListView". It has all you need...
0
 
LVL 1

Author Comment

by:duke_n
Comment Utility
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
 
LVL 20

Accepted Solution

by:
Madshi earned 100 total points
Comment Utility
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
 
LVL 1

Author Comment

by:duke_n
Comment Utility
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
 
LVL 1

Author Comment

by:duke_n
Comment Utility
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
 
LVL 20

Expert Comment

by:Madshi
Comment Utility
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
 
LVL 1

Author Comment

by:duke_n
Comment Utility
very well.

Thank you very much
0
 
LVL 1

Author Comment

by:duke_n
Comment Utility
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

Featured Post

How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

Join & Write a Comment

Preface This article introduces an authentication and authorization system for a website.  It is understood by the author and the project contributors that there is no such thing as a "one size fits all" system.  That being said, there is a certa…
Styling your websites can become very complex. Here I'll show how SASS can help you better organize, maintain and reuse your CSS code.
Viewers will learn about the different types of variables in Java and how to declare them. Decide the type of variable desired: Put the keyword corresponding to the type of variable in front of the variable name: Use the equal sign to assign a v…
The viewer will learn the basics of jQuery, including how to invoke it on a web page. Reference your jQuery libraries: (CODE) Include your new external js/jQuery file: (CODE) Write your first lines of code to setup your site for jQuery.: (CODE)

744 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

17 Experts available now in Live!

Get 1:1 Help Now