The discussion on VELVEx package as well as other VELVEx extensions is hosted by Jim Kueneman's news server at: news.mustangpeak.net/support.virtualshelltools (web based). Please, address general questions to the newsgroup.
Direct e-mail contact:
For information on VirtualTreeview, visit http://www.soft-gems.net
For information on VirtualShellTools, visit http://www.mustangpeak.net
License
The contents of this package are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this package except in compliance with the License.
You may obtain a copy of the License at: http://www.mozilla.org/MPL
Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License.
The initial developer of this package is Robert Lee.
Donations
VELVEx is free under the terms of the Mozilla Public License.
However, if you wish to express your appreciation for the time and resources the authors have expended developing and supporting it over the years, we do accept and appreciate donations.
Donations are accepted via either PayPal (preferred) or Kagi. Please click one of the following links to donate:
PayPal
Kagi
Thank you for your support.
Installation
Requirements:
- Mike Lischke's VirtualTreeview (http://www.soft-gems.net)
- Jim Kueneman's VirtualShellTools (http://www.mustangpeak.net)
VELVEx is now part of VSTools, you can get it at http://www.mustangpeak.net
You can download the self installing executable or install manually from the zip file.
However the latest versions and patches of VELVEx will be availbable here.
Installation instructions:
- If you have a previous version of VELVEx installed in the IDE remove it from Component->Install Packages, select VELVEx from the list and press the Remove button.
- Add the VELVEx 'Source' directory to Tools->Environment Options->Library->Library Path.
- Open the VirtualExplorerListviewExD*.dpk package corresponding to the IDE version, a dialog box will inform you that the resource file must be recreated, press OK, press Compile (do not Install the package) and close the package window, a dialog box will ask you to save the changes, press YES.
- Open the VirtualExplorerListviewExD*D.dpk design package corresponding to the IDE version, a dialog box will inform you that the resource file must be recreated, press OK, press Compile and then press Install, close the package window, a dialog box will ask you to save the changes, press YES.
Another way to install VELVEx is using Silverpoint MultiInstaller
Getting Started
VELVEx is installed under the "VirtualShellTools" tab in the Components Palette.
To start using it just drop a VELVEx component on the form and use the VirtualExplorerTreeview property to link it to a TVirtualExplorerTreeview component.
You can also use the ExplorerCombobox property to link it to a TVirtualExplorerCombobox component.
Set the Active property to true, it is a good practice to activate all the VSTools components at runtime on the Form.OnShow event.
For more information go to the reference section.
FAQ
- How do I add support for a graphic format?
- The selection is not updated when it is manually changed at runtime.
- How do I add a background image to VELVEx?
- How do I set the header default properties like sort order and column width?
- How do I keep the sort order after the root folder is changed or refreshed?
- How do I add a custom column to VELVEx?
- How do I add incremental search to VELVEx?
- Drag and Drop from VET/VELVEx to a VCL control
- Drag and Drop from VET/VELVEx to a VirtualTree
- How does the file notifier system works?
|
You must have a TGraphic descendant that handles the kind of image you would like to add, like Anders Melander's TGifImage for gifs image files, or a complete image library like Mike Lischke's GraphicEx.
Currently VELVEx automatically supports the following image libraries:
- GraphicEx: To add support for this image library just uncomment the following line in the VSToolsAddIns.inc file located in your VSTool Include directory:
//{$DEFINE USEGRAPHICEX}
- ImageEn: To add support for this image library just uncomment the following line in the VSToolsAddIns.inc file located in your VSTool Include directory:
//{$DEFINE USEIMAGEEN}
To support GIF and TIFF images you'll have to add GifLZW and TIFLZW units in your application, and set a few global variables, for more information read the ImageEn help file.
- Envision Image Library: To add support for this image library just uncomment the following line in the VSToolsAddIns.inc file located in your VSTool Include directory:
//{$DEFINE USEENVISION}
- ImageMagick: To add support for this image library just uncomment the following line in the VSToolsAddIns.inc file located in your VSTool Include directory:
//{$DEFINE USEIMAGEMAGICK}
You will also need the ImageMagick Delphi wrapper
If the image library you are using is not listed above, and it extends TGraphic class to add support for additional images then you only have to manually add the file extensions to the ExtensionsList property:
VLEVEx.ExtensionsList.Add('.gif');
And to remove an image file type:
VLEVEx.ExtensionsList.Remove('.gif');
If the file type is known by the shell (mpg, mpeg, avi, doc, xls, html, etc.) activate the UseShellExtraction property to let the shell extract the thumbnail.
And if you want to extract the thumbnail from a custom file format you can use the VELVEx thread-enabled custom thumbnailer capabilities.
For example the CustomThread demo shows how to extract the thumbnails from .pas files. |
You need to call SyncSelectedItems if the selection is changed at runtime and the ViewStyle is not vsxReport:
VELVEx.SelectAll(True); // selection changed
if VELVEx.ViewStyle <> vsxReport then
VELVEx.SyncSelectedItems; |
If the ViewStyle is in vsxReport mode just use the Background property.
When VELVEx is not in report mode you have to use the ListView_SetBkImage API to set the background image on the ChildListview:
var
BK: TLVBKImage;
begin
with VELVEx.TreeOptions do
PaintOptions := PaintOptions + [toShowBackground];
// LVBKIF_SOURCE_HBITMAP is not supported by ListView_SetBkImage
// TLVBKImage.hbm is not supported by ListView_SetBkImage
Fillchar(BK, SizeOf(BK), 0);
BK.ulFlags := LVBKIF_SOURCE_URL or LVBKIF_STYLE_TILE;
BK.pszImage := PChar(Filename);
ListView_SetBkImage(LV.ChildListview.Handle, @BK);
end; |
You have to use OnHeaderRebuild to reset the default column properties.
OnHeaderRebuild is called everytime the root folder is changed or refreshed.
procedure TForm1.VELVExHeaderRebuild(
Sender: TCustomVirtualExplorerTree; Header: TVTHeader);
var
C: TVirtualTreeColumn;
begin
// Sort by size, descending
Header.SortColumn := 1;
Header.SortDirection := sdDescending;
Sender.Sort(nil, Header.SortColumn, Header.SortDirection);
// Show the Atributes column only on filesystem folders
if Assigned(Sender.RootFolderNamespace) then
if Sender.RootFolderNamespace.FileSystem then begin
if IsWinXP then
C := Header.Columns[6]
else
C := Header.Columns[4];
C.Options := C.Options + [coVisible];
end;
end; |
You have to use OnRootRebuild to save the current sort column and then use OnHeaderRebuild to reset the default column order.
var
CurrentSortColumn: TColumnIndex;
CurrentSortDirection: TSortDirection;
procedure TForm1.VELVExRootRebuild(
Sender: TCustomVirtualExplorerTree);
begin
// Save the current sort column
if Assigned(VELVEx.RootFolderNamespace) then begin
CurrentSortColumn := VELVEx.Header.SortColumn;
CurrentSortDirection := VELVEx.Header.SortDirection;
end;
end;
procedure TForm1.VELVExHeaderRebuild(
Sender: TCustomVirtualExplorerTree; Header: TVTHeader);
begin
// Reset the default sort order
Header.SortColumn := CurrentSortColumn;
Header.SortDirection := CurrentSortDirection;
Sender.Sort(nil, Header.SortColumn, Header.SortDirection);
end; |
In this example we are going to add a column for the files extension
1) Use OnHeaderRebuild to add the column and store the
column index.
var
MyCustomColumnIndex: Integer = -2;
procedure TForm1.VELVExHeaderRebuild(
Sender: TCustomVirtualExplorerTree; Header: TVTHeader);
var
Column: TVirtualTreeColumn;
begin
// Add the column if we are on a file system folder
if Sender.RootFolderNamespace.FileSystem then begin
Column := Header.Columns.Add;
Column.Text := 'Extension';
Column.Width := 50;
Column.Position := 1;
MyCustomColumnIndex := Column.Index;
// Note: Column.Position <> Column.Index, never change the
// Column.Index
// VT sorts by Column.Index and shows the columns in the header
// by Column.Position
end
else
MyCustomColumnIndex := -2; // -1 is used by VT
end;
2) Use OnGetVETText to retrieve the text for the column
procedure TForm1.VELVExGetVETText(Sender: TCustomVirtualExplorerTree;
Column: TColumnIndex; Node: PVirtualNode; Namespace: TNamespace;
var Text: WideString);
begin
if Column = MyCustomColumnIndex then
Text := Namespace.Extension;
end;
3) Use OnCustomColumnCompare to sort the column
procedure TForm1.VELVExCustomColumnCompare(
Sender: TCustomVirtualExplorerTree; Column: TColumnIndex;
Node1, Node2: PVirtualNode; var Result: Integer);
var
NS1, NS2: TNamespace;
begin
if (Column = MyCustomColumnIndex) and
LV.ValidateNamespace(Node1, NS1) and
LV.ValidateNamespace(Node2, NS2) then
Result := WideCompareText(NS1.Extension, NS2.Extension);
end; |
Just use the OnIncrementalSearch event:
uses
VirtualWideStrings, VirtualShellUtilities;
function My_StrLICompW(W1, W2: PWideChar;
MaxLen: Cardinal): Integer;
begin
W1 := StrLCopyW(W1, W1, MaxLen);
W2 := StrLCopyW(W2, W2, MaxLen);
Result := StrICompW(W1, W2);
end;
procedure TForm1.VELVExIncrementalSearch(Sender: TBaseVirtualTree;
Node: PVirtualNode; const SearchText: WideString;
var Result: Integer);
var
NS: TNamespace;
W1, W2: PWideChar;
begin
//Adapted from the VT Advanced demo (PropertiesDemo.pas):
if VELVEx.ValidateNamespace(Node, NS) then begin
W1 := PWideChar(SearchText);
W2 := PWideChar(NS.NameInFolder);
Result := My_StrLICompW(W1, W2, StrLenW(W1));
end;
end; |
VSTools controls and the Shell uses OLE D&D, not VCL D&D.
The easiest way to make a VCL control OLE D&D aware is to do it with the D&D Component Suite: http://www.angusj.com/delphi/
The cheap and dirty way is to catch the WM_DROPFILES message of the TWinControl:
type
TForm1 = class(TForm)
ListBox1: TListbox;
procedure FormClose(Sender: TObject;
var Action: TCloseAction);
procedure FormShow(Sender: TObject);
protected
procedure Hook(var Msg: TMessage);
end;
THackListbox = class(TListbox);
uses
ShellAPI;
procedure TForm1.Hook(var Msg: TMessage);
// Window Procedure Hook for the Listbox so we can look
// for the WM_DROPFILES message.
var
Count, Size, I: Integer;
S: String;
begin
if Msg.Msg = WM_DROPFILES then begin
Listbox1.Items.BeginUpdate;
try
Count := DragQueryFile(Msg.wParam, $FFFFFFFF, nil, 0);
for I := 0 to Count - 1 do begin
Size := DragQueryFile(Msg.wParam, I, nil, 0);
SetLength(s, Size - 1); // Don't need the null
DragQueryFile(Msg.wParam, i, PChar(s), Size);
Listbox1.Items.Add(s);
end;
finally
Listbox1.Items.EndUpdate;
end;
end
else
THackListbox(ListBox1).WndProc(Msg);
end;
procedure TForm1.FormShow(Sender: TObject);
begin
DragAcceptFiles(Listbox1.Handle, True);
Listbox1.WindowProc := Hook
end;
procedure TForm1.FormClose(Sender: TObject;
var Action: TCloseAction);
begin
DragAcceptFiles(Listbox1.Handle, False);
end; |
Use the THDrop class to get the Just
uses
ActiveX, VirtualDataObject;
function DataObjectIsHDrop(D: IDataObject): Boolean;
// Returns true if the DataObject supports CF_HDROP
var
FormatEtc: TFormatEtc;
begin
FormatEtc.cfFormat := CF_HDROP;
FormatEtc.ptd := nil;
FormatEtc.dwAspect := DVASPECT_CONTENT;
FormatEtc.lindex := -1;
FormatEtc.tymed := TYMED_HGLOBAL;
Result := Succeeded(D.QueryGetData(FormatEtc));
end;
procedure DataObjectExtractFileList(D: IDataObject;
FileList: TWideStringList);
// Returns the DataObject's file list
var
HDRop: THDrop;
I: integer;
begin
FileList.Clear;
if DataObjectIsHDrop(D) then begin
HDrop := THDrop.Create;
try
if HDrop.LoadFromDataObject(D) then
for I := 0 to HDrop.FileCount - 1 do
FileList.Add(HDrop.FileName(I));
//Note: there's no overloaded FileNames method
//to handle TWideStringList
finally
HDrop.Free;
end;
end;
end;
procedure TForm1.VirtualStringTree1DragOver(Sender: TBaseVirtualTree;
Source: TObject; Shift: TShiftState; State: TDragState; Pt: TPoint;
Mode: TDropMode; var Effect: Integer; var Accept: Boolean);
begin
Accept := DataObjectIsHDrop(Sender.DragManager.DataObject);
end;
procedure TForm1.VirtualStringTree1DragDrop(Sender: TBaseVirtualTree;
Source: TObject; DataObject: IDataObject;
Formats: TFormatArray; Shift: TShiftState; Pt: TPoint;
var Effect: Integer; Mode: TDropMode);
var
DroppedFileList: TWideStringList;
begin
DroppedFileList := TWideStringList.Create;
try
FileListFromDataObject(DataObject, DroppedFileList);
finally
DroppedFileList.Free;
end;
end; |
The notifier system (the one that can be turned on in the properties) uses a recently documented subsystem in windows. You register a window for the subsystem to send message to when applications call SHChangeNotify to tell Windows a change in something occurred. When you get this message you extract a PIDL or 2 PIDLs out of the information. If something is created you get one, if something is changed you get 2, one PIDL for what the object was and one PIDL for what the object is now.
Every version of Windows is different in how they handle this subsystem (that is why it was undocumented). Windows 2k was excellent, it sent notifications for every file event. Other systems have an overflow threshold. If too many files are deleted the system would send a few individual notifications then it would overflow and send an "updateDirectory" event, including XP.
Due to the fact you can't rely on this and the performance implications during large file manipulations I decided to take all file changes and combine them into "updateDirectory" event. This all happens in the change notifier thread. Also the subsystem will often send the same notification several times with some operating systems.
If you have the Kernel notification enabled in VET (see the VirtualExplorer demo) then you get more duplicates so all in all filtering all file operation into an "updateDirectory" event was the best way to go.
There are 3 threads if you have Shell and Kernel notifiers on.
One thread handles the Shell notifications, TVirtualShellChangeThread.
One thread handles the Kernel notifications, TVirtualKernelChangeThread.
They each filter the events into directories updates.
They then add an event to a third, TVirtualChangeDispatchThread, that caches up the events for some defined time, VIRTUALSHELLNOTIFYREFRESHRATE.
When the event is added it is first checked to see if it is a duplicate event, if it is it is not added and is freed.
This third thread does a PostMessage to a window that was created by the TVirtualChangeNotifier object. This objects job is to receive the notification in the context of the main thread then dispatch it to all the windows in the application that have registered themselves.
Note that ANY window can register itself not just VET windows.
The registered windows will receive a WM_SHELLNOTIFY message.
The PIDLs sent by the notification system are not "real" PIDLs.
They are half baked PIDLs that will not work for some IShellFolder methods, for instance CompareID will fail when comparing them with each other.
What is actually sent to this object is a TThreadList of all events.
The thread list is reference counted. I set the reference count of the thread list to the number of registered windows (the window list is also a thread list just in case) then does a PostMessage to each window with the TThreadList as a parameter. As each window receives the message it does what it need to do with the events in the list then calls "Release" on the thread list. This should keep the list valid until the last registered window is finished with the list.
On the last call to Release the list destroys itself.
-Jim |
|
|