Lazarus

Programming => LCL => Topic started by: jamie on August 16, 2019, 03:01:58 am

Title: TShellTreeView, How to mask file extensions?
Post by: jamie 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?
Title: Re: TShellTreeView, How to mask file extensions?
Post by: jamie 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.
Title: Re: TShellTreeView, How to mask file extensions?
Post by: wp 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.
Title: Re: TShellTreeView, How to mask file extensions?
Post by: Bart 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
Title: Re: TShellTreeView, How to mask file extensions?
Post by: wp 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.
Title: Re: TShellTreeView, How to mask file extensions?
Post by: Bart 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
Title: Re: TShellTreeView, How to mask file extensions?
Post by: jamie 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.
Title: Re: TShellTreeView, How to mask file extensions?
Post by: wp 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.
Title: Re: TShellTreeView, How to mask file extensions?
Post by: jamie 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?
Title: Re: TShellTreeView, How to mask file extensions?
Post by: Bart 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
Title: Re: TShellTreeView, How to mask file extensions?
Post by: wp 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.
Title: Re: TShellTreeView, How to mask file extensions?
Post by: Bart 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
Title: Re: TShellTreeView, How to mask file extensions?
Post by: jamie 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
Title: Re: TShellTreeView, How to mask file extensions?
Post by: jamie 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.

Title: Re: TShellTreeView, How to mask file extensions?
Post by: Bart 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
Title: Re: TShellTreeView, How to mask file extensions?
Post by: wp on August 17, 2019, 12:23:39 pm
Bart, I think it is required that the method PopulateTreeNodeWithFiles() is virtual, otherwise it will not be called.
Title: Re: TShellTreeView, How to mask file extensions?
Post by: Bart on August 17, 2019, 01:38:30 pm
It can be made virtual if so requested.
We've done that before.

Bart
Title: Re: TShellTreeView, How to mask file extensions?
Post by: jamie on August 17, 2019, 08:46:38 pm
https://stackoverflow.com/questions/15319154/how-to-filter-tshelllistview-items-to-a-specific-file-extension

 That was a quick search I did and I guess I am not the only one that needs such a feature on a single control. At least D7 had a OnAddItem or what ever they called it.

 Its better than nothing.

 I get the feeling that I have disrupted someone's holy grail of code and there just isn't going to be any changing it, even when all it does is improves it with no crutches of current support.

 So why even allow it to show archive files at all since what you want users to do is be pushed/forced to use  ShellListview as an additional control. I don't want all that on the screen, I need the space for more important features.
   
 I wanted this to be as close to cross platform as possible but if this going to turn into a issue of wits, I'll pass and implement a full windows pane view.


Title: Re: TShellTreeView, How to mask file extensions?
Post by: Bart on August 17, 2019, 10:27:09 pm
I get the feeling that I have disrupted someone's holy grail of code and there just isn't going to be any changing it, even when all it does is improves it with no crutches of current support.

Well, excuse me for having an opinion about this (and having done work on TShellTreeView).
I'll refrain from any further comment in this topic.
I'll remind myself not to reply to anything you say on this forum.

Bart
Title: Re: TShellTreeView, How to mask file extensions?
Post by: jamie on August 18, 2019, 12:12:36 am
Well thank you very much, I guess I got my point across.

 And btw, image files are not what I am after to mask, that was only example. This app needs to look at hardware files and I need to keep them in view at all times while I work to the right because the mask property will be getting changed through out the process.

 This app must look and feel like the other tools that get used in conjunction, we are not dealing with just computer script writers we are dealing with Electronic, electrical techs that understand automation programs and how most of the interfaces work, in this case there is a good deal of a work load using different file types that need to be in view of the folder tree as part of the working scope on the form.

 Some people actually use or tried to Lazarus to make difficult applications.
 
 Sorry for the confusion, but that's the way it is.

 I need to update my Delphi Dev package, I have a feeling I may need to move it to that.