Home SpTBXLib VELVEx MultiInstaller Skin Editor

Support

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

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

  1. How do I add support for a graphic format?
  2. The selection is not updated when it is manually changed at runtime.
  3. How do I add a background image to VELVEx?
  4. How do I set the header default properties like sort order and column width?
  5. How do I keep the sort order after the root folder is changed or refreshed?
  6. How do I add a custom column to VELVEx?
  7. How do I add incremental search to VELVEx?
  8. Drag and Drop from VET/VELVEx to a VCL control
  9. Drag and Drop from VET/VELVEx to a VirtualTree
  10. How does the file notifier system works?
1. How do I add support for a graphic format?

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.

2. The selection is not updated when it is manually changed at runtime.

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;
3. How do I add a background image to VELVEx?

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;
4. How do I set the header default properties like sort order and column width?

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;
5. How do I keep the sort order after the root folder is changed or refreshed?

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;
6. How do I add a custom column to VELVEx?

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;
7. How do I add incremental search to VELVEx?

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;
8. Drag and Drop from VET/VELVEx to a VCL control

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;
9. Drag and Drop from VET/VELVEx to a VirtualTree

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;
10. How does the file notifier system works?

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