Recent

Author Topic: TShellTreeView, How to mask file extensions?  (Read 987 times)

jamie

  • Hero Member
  • *****
  • Posts: 2140
TShellTreeView, How to mask file extensions?
« on: August 16, 2019, 03:01:58 am »
I would like to use this control because it would be nice to put this on a tabsheet and it does work that way however, I don't see any premade way to set the filter mask for the files I want to view only.

 I am interested in viewing a series of image file extensions but the way it works now it is showing all file types when I enable the object types to do so.

 Also, I see there is a Method in there "GetFilesInDir" which accepts a MASK string to specify the types I want accumulated but not for the viewing of the TreeView ? It shows all the files and I only what the files of interest to show in the branches..

 Where did I sleep?
Number 1 at blue screen app creations!

jamie

  • Hero Member
  • *****
  • Posts: 2140
Re: TShellTreeView, How to mask file extensions?
« Reply #1 on: August 16, 2019, 04:09:12 am »
Digging In deeper it's apparent there is no way designed into it? I find it hard to believe this has been this way from day one?

 It is setup to look at all files with no previsions to select the file types you want. Can we honestly believe this is correct?

 Otherwise I don't see any purpose for this control even being installed because it's basically useless the way it is now!

 I can use Dialogs to do this but, I don't want dialogs.

 It looks like I can fix this if I add a property and a field "fMask" and default it to "*" initially unless its set in the designer.
Number 1 at blue screen app creations!

wp

  • Hero Member
  • *****
  • Posts: 6450
Re: TShellTreeView, How to mask file extensions?
« Reply #2 on: August 16, 2019, 11:15:10 am »
Also, I see there is a Method in there "GetFilesInDir" which accepts a MASK string to specify the types I want accumulated but not for the viewing of the TreeView ? It shows all the files and I only what the files of interest to show in the branches..

I tried to introduce a Mask property to be used by GetFilesInDir. The consequence, however, is that the Mask is applied also to directory names, and this is probably not what you want. Certainly, this can be fixed but requires some larger changes with more intense testing than I can do at the moment...

Then I tried to introduce an event OnAddingFile with a boolean var-parameter "Accept" to prevent a file from inclusion in the file list under some circumstances. But this failed too because GetFilesInDir is a class procedure which does not have access to the event... Again, this can be made, but would break the code in existing projects using GetFilesInDir without creating a class instance.

So, in total, no easy way...

Why don't you combine a folders-only ShellTreeView with ShellListView? That's way I always have been using TShellTreeView. And, in fact, it is very useful this way.
« Last Edit: August 16, 2019, 11:18:40 am by wp »
Lazarus trunk / fpc 3.0.4 / all 32-bit on Win-10

Bart

  • Hero Member
  • *****
  • Posts: 3546
    • Bart en Mariska's Webstek
Re: TShellTreeView, How to mask file extensions?
« Reply #3 on: August 16, 2019, 01:02:19 pm »
Does Delphi's TTreeView support this? If so, file a bugreport please.
I second wp's advice to use a mask for the attached TShellListView?

Bart

wp

  • Hero Member
  • *****
  • Posts: 6450
Re: TShellTreeView, How to mask file extensions?
« Reply #4 on: August 16, 2019, 01:22:56 pm »
Does Delphi's TTreeView support this?
In current Delphi XE 10.3, I don't see TShellTreeView any more, it did exist in Delphi 7, though. But even this was without a "Mask" property (there was an OnAddFolder event, though, with a "CanAdd" var-parameter - but we are talking here about files, not folders).

I remember the Delphi7 ShellTreeView as being extremely hard to use and fragile. The Lazarus ShellTreeView (and ShellListView) are much more stable and very straightforward to use.
Lazarus trunk / fpc 3.0.4 / all 32-bit on Win-10

Bart

  • Hero Member
  • *****
  • Posts: 3546
    • Bart en Mariska's Webstek
Re: TShellTreeView, How to mask file extensions?
« Reply #5 on: August 16, 2019, 03:59:43 pm »
An optional paramter could be used to declare wether the supplied mask is for both files and folders (the default behaviour) or for files only. Not very pretty, I know.
It could be done, it probably would not break any code.

But IMO TShellTreeView simply wasn't designed for this kind of thing.
Personally I don't even like that it can show files at all.

Bart

jamie

  • Hero Member
  • *****
  • Posts: 2140
Re: TShellTreeView, How to mask file extensions?
« Reply #6 on: August 16, 2019, 04:35:51 pm »
I think I can attack the issue for now by enumerating the items and deleting any nodes that
have a .??? that does not match my filter.. I noticed the ItEMS property was exposed by another user in some bug report but this isn't a very good way to do this because it creates resources and then releases them...

 I'll dig deeper,.. Thanks for taking some time on it.
Number 1 at blue screen app creations!

wp

  • Hero Member
  • *****
  • Posts: 6450
Re: TShellTreeView, How to mask file extensions?
« Reply #7 on: August 16, 2019, 04:43:07 pm »
But IMO TShellTreeView simply wasn't designed for this kind of thing.
Personally I don't even like that it can show files at all.
Like myself.
Lazarus trunk / fpc 3.0.4 / all 32-bit on Win-10

jamie

  • Hero Member
  • *****
  • Posts: 2140
Re: TShellTreeView, How to mask file extensions?
« Reply #8 on: August 16, 2019, 07:11:30 pm »
Ok, I've come up with a fix that shouldn't cause any existing code issues..

I inserted a couple of lines and added a fMask property. I didn't make it published, I guess I could but my method of setting the mask property is this mask := '.jpg.bmp.exe.etc';

 by default if the mask is empty it'll just behave like it did and if there is no archives in the return
list then it just ignores this mask and carries on..
In the "PopulateTreeNodeWIthFiles" method
Code: Pascal  [Select]
  1.  
  2.     for i := 0 to Files.Count - 1 do
  3.     begin
  4.       if (fmask ='') or(TFileItem(Files.Objects[i]).FileInfo.attr and faArchive = 0) or     //added
  5.       ( Pos(UpperCase(ExtractFileExt(TFileItem(Files.Objects[i]).FileInfo.Name)),UpperCase(FMask))<>0) Then //added
  6.      Begin  //added
  7.       NewNode := Items.AddChildObject(ANode, Files.Strings[i], nil);
  8.       TShellTreeNode(NewNode).FFileInfo := TFileItem(Files.Objects[i]).FileInfo;
  9.       TShellTreeNode(NewNode).SetBasePath(TFileItem(Files.Objects[i]).FBasePath);
  10.  
  11.       if (fObjectTypes * [otNonFolders] = []) then
  12.         NewNode.HasChildren := (TShellTreeNode(NewNode).IsDirectory and
  13.                                HasSubDir(AppendpathDelim(ANodePath)+Files[i]))
  14.       else
  15.         NewNode.HasChildren := TShellTreeNode(NewNode).IsDirectory;
  16.      end;
  17.     end;
  18.  

LazusInstallDir\Lcl\ShellCtrls.pas

I also added the property for Mask but I didn't make a setter which I should to refresh the list?
« Last Edit: August 16, 2019, 08:27:55 pm by jamie »
Number 1 at blue screen app creations!

Bart

  • Hero Member
  • *****
  • Posts: 3546
    • Bart en Mariska's Webstek
Re: TShellTreeView, How to mask file extensions?
« Reply #9 on: August 16, 2019, 11:09:52 pm »
Your use of Pos() to determine wether the mask matches is wrong.
You could use MatchesMaskList() function for that instead ...

I don't think we will incorporate such behaviour in TShellTreeView though.

You could maybe override PopulateTreeNodeWithFiles in a derived class?

Bart

wp

  • Hero Member
  • *****
  • Posts: 6450
Re: TShellTreeView, How to mask file extensions?
« Reply #10 on: August 16, 2019, 11:29:43 pm »
I think you get more flexibility when you use an event "OnAddItem" which gets the entire SearchRec as a parameter and has a boolean var-parameter CanAdd which must be set to false to exclude the file/folder from the tree:

Code: Pascal  [Select]
  1. type
  2.   TAddItemEvent = procedure(Sender: TObject; const ABasePath: String;
  3.     const AFileInfo: TSearchRec; var CanAdd: Boolean) of object;
  4.  
  5.   TCustomShellTreeView = class(TCustomTreeView)
  6.   private
  7.     ...
  8.     FOnAddItem: TAddItemEvent;
  9.   protected
  10.     ...
  11.     property OnAddItem: TAddItemEvent read FOnAddItem write FOnAddItem;
  12.   end;
  13.  
  14.   TShellTreeView = class(TCustomShellTreeView)
  15.   ...
  16.   published
  17.     property OnAddItem;
  18. ...
  19.  
  20. function TCustomShellTreeView.PopulateTreeNodeWithFiles(
  21.   ANode: TTreeNode; ANodePath: string): Boolean;
  22. var
  23.   i: Integer;
  24.   Files: TStringList;
  25.   NewNode: TTreeNode;
  26.   canAdd: Boolean;   // <<< new
  27. ...
  28. begin
  29. ...
  30.   Files := TStringList.Create;
  31.   try
  32.     Files.OwnsObjects := True;
  33.     GetFilesInDir(ANodePath, AllFilesMask, FObjectTypes, Files, FFileSortType);
  34.     Result := Files.Count > 0;
  35.  
  36.     for i := 0 to Files.Count - 1 do
  37.     begin
  38.       // <<<<<<<<< new from here....
  39.       canAdd := true;
  40.       if Assigned(FOnAddItem) then
  41.         with TFileItem(Files.Objects[i]) do
  42.           FOnAddItem(Self, FBasePath, FileInfo, canAdd);
  43.       if not canAdd then
  44.         Continue;
  45.     // ... to here >>>>>>>>>>>>>>
  46.  
  47.       NewNode := Items.AddChildObject(ANode, Files.Strings[i], nil);
  48.       ....
  49.  

The following event handler, for example, filters all files with extensions ".pas" and ".pp" which were created this year:

Code: Pascal  [Select]
  1. procedure TForm1.ShellTreeView1AddItem(Sender: TObject;
  2.   const ABasePath: String; const AFileInfo: TSearchRec; var CanAdd: Boolean);
  3. var
  4.   ext: String;
  5. begin
  6.   ext := Lowercase(ExtractFileExt(AFileInfo.Name));
  7.   CanAdd := (AFileInfo.Attr and faDirectory <> 0) or (
  8.     (YearOf(FileDateToDateTime(AFileInfo.Time)) = 2019) and ((ext = '.pas') or (ext = '.pp'))
  9.   );
  10. end;

Delphi 7 has an OnAddFolder event handler for the same purpose; it is not compatible though because it has a TShellObj parameter instead of the TSearchRec.
Lazarus trunk / fpc 3.0.4 / all 32-bit on Win-10

Bart

  • Hero Member
  • *****
  • Posts: 3546
    • Bart en Mariska's Webstek
Re: TShellTreeView, How to mask file extensions?
« Reply #11 on: August 16, 2019, 11:50:16 pm »
That would probably make a sensible addition.

W.r.t. coding style I prefer
Code: Pascal  [Select]
  1.   if canAdd then
  2.   begin
  3.    //rest of original code
  4.   end;

over:
Code: Pascal  [Select]
  1. if not canAdd then
  2.         Continue;

Since Lazarus TShellTreeView is not really compatible with Delphi's one anyway, IMO such a thing could be implemented.

Bart

jamie

  • Hero Member
  • *****
  • Posts: 2140
Re: TShellTreeView, How to mask file extensions?
« Reply #12 on: August 17, 2019, 12:13:24 am »
I have coded a mile long amount of code around this addition and it's working perfectly.

The mask settings may not be to your liking, that I can change, but the way its working now does not change any old (bad) habits that it currently has in the distro.
 
  This was the easiest fix that is effective without adding events. This behavior is almost equal to that of a file dialog which I don't want in this case.

  I can change the mask filtering test to clean it up, this was a preliminary start and I will agree with that being a little of a mess but that is what I did at the time. What ever changes I make need to conform to what I have now in functionality.

  Having an OnItemAdd event is ok too but I still want the Mask Filter option in there.

 I'll look at using the suggested replacement function for testing the mask.

 Thanks
Number 1 at blue screen app creations!

jamie

  • Hero Member
  • *****
  • Posts: 2140
Re: TShellTreeView, How to mask file extensions?
« Reply #13 on: August 17, 2019, 01:15:37 am »

Code: Pascal  [Select]
  1.    for i := 0 to Files.Count - 1 do
  2.     begin
  3.       if (fmask ='') or(TFileItem(Files.Objects[i]).FileInfo.attr and faArchive = 0) or     //added
  4.       (MatchesMaskList(ExtractFileExt(TFileItem(Files.Objects[i]).FileInfo.Name),fMask)) Then //added
  5.      Begin  //added
  6.       NewNode := Items.AddChildObject(ANode, Files.Strings[i], nil);
  7.       TShellTreeNode(NewNode).FFileInfo := TFileItem(Files.Objects[i]).FileInfo;
  8.       TShellTreeNode(NewNode).SetBasePath(TFileItem(Files.Objects[i]).FBasePath);
  9.  
  10.       if (fObjectTypes * [otNonFolders] = []) then
  11.         NewNode.HasChildren := (TShellTreeNode(NewNode).IsDirectory and
  12.                                HasSubDir(AppendpathDelim(ANodePath)+Files[i]))
  13.       else
  14.         NewNode.HasChildren := TShellTreeNode(NewNode).IsDirectory;
  15.      end;
  16.     end;  
  17.                                                                                                    
  18.  

 This is the final editing for me, I am using it and if you decide you don't that's just fine but I need this to function like this and I am not writing out a mile long source code just to fix a little issue here that is not going to cause any problems for anyone else, if anything I think many will be happy about it, I know I am it saves me lots of time and does not hurt any existing code that I can see.
 
 If you don't want to use this or allow its functionality that's fine, I'll just keep my install working this way. I want something that works with the OI and I am not writing a new control just for this little nitch.

Number 1 at blue screen app creations!

Bart

  • Hero Member
  • *****
  • Posts: 3546
    • Bart en Mariska's Webstek
Re: TShellTreeView, How to mask file extensions?
« Reply #14 on: August 17, 2019, 12:04:16 pm »
I want something that works with the OI and I am not writing a new control just for this little nitch.

Hack the system then.

You only override the method in question.
You give the control the same classname:
Code: Pascal  [Select]
  1. unit MyShellTree;
  2. ...
  3. type
  4.   TShellTreeView = class(ShellCtrls.TShellTreeView)
  5.   ...
  6.   end;
  7.  

Then in your project simply use MyShellTree after ShellCtrls.
Drop a regular TShellTreeView on the form.
At runtime this will now become an instance of MyShellTree.TShellTreeView.
It will stream (=load) OK, because you did not alter any published property.

Bart