From Delphi 6 onwards, Borland provides shell controls including TShellTreeView
and TShellListView that mimic the functionality of Windows Explorer but they are
tucked away on the Samples page of the palette, have no documentation and even their source can
be hard to find; its in Delphi\Demos\ShellControls. You could be forgiven for thinking they are
an afterthought that you're not expected to use.
This article first appeared in the Pascal Newsletter Issue 48.
Recently, I wanted to build my own FTP client because I don't like any of the free ones I've tried and I thought 'I've got Indy' so how hard can it be? I checked out the demo for TIdFTP, the Indy FTP client component, and the networking end looked pretty easy so I started to think about design. I wanted something simple and decided on a view of the local file system above a view of the remote file system with the main toolbar in-between. Each view would contain a TreeView and a ListView with a few buttons for simple, Explorer-like navigation. I also wanted drag and drop between controls and with Explorer. At this point I went looking for components to implement the local side and discovered Borland's shell controls. I decided that this kind of layout is something that I could reuse so I started working on a generic frame.

Figure 1 - TconExplorerFrame
So how do they work? Well some functionality is easy to implement but in other ways these controls can be awkward and confusing. Most of the methods you'd expect to find either don't exist or return parameters that are of dubious value. Often they are the wrong type for other calls you want to make. What should have been a couple of hours of easy programming rapidly turned into several nights of reading source code, experimentation and hair pulling. At some point during this process I decided to turn it into an article so I could share my pain with you. ;)
Let's start with the easy stuff. I connected my TShellTreeView to a
TShellListView and then started on a toolbar. The first button I wanted was one to
move up the directory tree and after a bit of looking
I realised that the TShellListView.Back method would do this for me. Most of the other
buttons I wanted were more difficult so I'll come back to them but a Views button for the
TShellListView was easy. I just created a popup menu for my
button that sets TShellListView.ViewStyles. At this point I had a very simple file
manager that provides the standard Explorer context menu and basic navigation features.
I considered adding a TShellComboBox above the file list. I wanted it to resize
with the frame like the other controls but it doesn't have an Align property. I tried
using Anchors but could not get the effect I wanted so I scrapped that idea.
Now on to the more complicated stuff. The shell controls don't provide methods to help us
manipulate files so we need to use the Windows API. The SHFileOperation() function
can perform Copy, Move, Delete and Rename operations so I wrote the wrapper function FileOperation() to make it easier to use.
This function returns true if the operation is a success and displays a progress dialog if
necessary. Look-up SHFILEOPSTRUCT in the WinAPI help to see the possible values of
op and flags. I still can't decide if I'll need to access this function from outside my frame so
for the moment it is a private method but I may change this in the future.
A Delete button was now simple, all I had
to do was determine which file or folder is selected and delete it using FileOperation().
While doing a Refresh button I decided to
write a generic function that could be called from other methods and would refresh both controls.
TShellTreeView.Refresh takes a node as a parameter, but which node to pass? I tried
passing the current folder but that didn't always work (this also seems to be a problem in
Explorer). Then I tried passing the root node and this works properly. TShellListView
flickers when we refresh a TShellTreeView it is connected to so I detach them first.
See the procedure TconExplorerFrame.Refresh
in the source.
When creating a new folder we need to give it a unique name. The usual way to do this is to
call it 'New Folder' and add a number to the name if that folder already exists. I wrote a GetNewFolderName() function that would
return the unique name I needed using the DirectoryExists() function from
SysUtils.pas and a while loop. My Create
Folder button calls this function and then uses CreateDir() from SysUtils.pas.
I wanted to provide a Properties button
but the shell controls don't have any useful methods. Since pressing Alt+Enter in a
TShellListView works I dived into ShellCtrls.pas and checked out the
source. Initially it looked simple, all you need to do is call DoContextMenuVerb. Or
not, because DoContextMenuVerb is not a method of TShellListView but is
a procedure private to ShellCtrls.pas. At this stage I decided plagiarism was the
easiest course and copied it into my frame unit as a private method.
Double-clicking files in TShellListView doesn't work (in Win2k at least) but
choosing Open from the context menu does. Checking the source it does have a DblClick method that
calls ShellExecute(). At this point I noticed TShellFolder.ExecuteDefault.
Since a TShellFolder can be a file or a folder and we can get the selected item as a
TShellFolder by calling TShellListView.SelectedFolder, writing an
OnDblClick event method was easy. This also ensures that double-click doesn't just
try to open the file but performs the default action from its context menu which is what Explorer
does. See TconExplorerFrame.shlllstvwFilesDblClick.
At this point I had everything I wanted except Drag and Drop. I'd never done Drag and Drop
before so I had to do some reading before I started. Ideally I would like to be able to change
the cursor if the user presses Ctrl, like Explorer does. This means using a TDragControlObject
to supply a drag image list so I decided to keep things simple for now and leave that effect out.
I started with dragging from my TShellListView to my TShellTreeView.
The methods and properties necessary (with the right return types) don't seem to exist until you
realise the SelectedFolder property can return files as well as folders. I wrote an OnDragOver event for the
TShellTreeView so that it would accept items from the TShellListView and
started on its OnDragDrop event.
I quickly found I couldn't access the file being dragged from this event so decided to store it in
a variable global to my frame during TShellListView.OnStartDrag and
then clear it in TShellListView.OnEndDrag.
I had trouble with the target folder too, TShellTreeView.GetNodeAt and
TShellTreeView.DropTarget return a TTreeNode but to get the path for a
file operation I wanted a TShellFolder so I Select the DropTarget
to retrieve the SelectedFolder (a TShellFolder) and then Select
the previous folder again. This makes the TShellListView flicker horribly (you can
actually see it change dir) so I tried using TShellListView.Items.BeginUpdate and
EndUpdate but this didn't work so I had to detach it from its TShellTreeView,
perform the select operations and then re-attach it. This is nasty and I don't like it but it works.
The OnDragDrop event doesn't
provide any information about the keyboard and I want a copy operation if the user is pressing Ctrl
at the end of the drag. I used the GetKeyState() function from the Jedi Code Library
(JCLSysInfo.pas) for this.
Having got drag working from my TShellListView I changed my OnDragOver and OnDragDrop events to also
accept a folder dragged from my TShellTreeView and addedOnStartDrag and OnEndDrag events to it. These
six events provide all the features required for dropping a file or folder on the
TShellTreeView from within the frame. To keep it simple I only allow the user to
select and drag one item at a time.
Explorer allows you to drag a folder from the tree to the file list and to drag files to
folders within the list. But dragging a folder from TShellTreeView selects and
displays that folder and in any case TShellListView.DropTarget always returns nil!
Because of this I couldn't find any way to implement these features. :(
Having done what I could to provide Drag and Drop within my frame I now wanted to make it
work with Explorer. To accept a file dropped from Explorer we use Windows messages and I was
unsure if this would effect my Drag and Drop events but I was pleased to find that it didn't. I
did have a few problems getting it properly setup though. We need to call
DragAcceptFiles() with the handle of the control that will accept files to let
Windows know to send it the drop files message but TFrame doesn't have an
OnCreate event and we can't refer to its components or itself in an initialization
section. I had wanted my frame to be fully encapsulated but I had to settle for calling
DragAcceptFiles() in the OnCreate event of the form that contains it.
Initially I wanted to pass the handle to my TShellListView so that files could only
be dropped there but that would need a WMDROPFILES message for the
TShellListView so I ended up accepting files anywhere on the frame by passing its
handle. Once I got round these problems the rest was easy.
The procedure TconExplorerFrame.WMDROPFILES
handles the drop message. It uses DragQueryFile() from the WinAPI to determine the
number of items being dropped and then uses it again to get an item's full path as it iterates
through the list. Windows automatically provides us with a Copy cursor and pressing Ctrl or Shift
doesn't effect it so I copy the items to the folder currently displayed in my TShellListView.
I had also wanted to be able to drag files to Explorer or other instances of my test program
but I've been unable to find any articles or tips that explain how to do it. I had hoped that
enabling drag from Explorer might have given me one of these features as a side-effect or helped
me work out how to do them but it didn't. I think I need to create my own descendant of
TCustomShellListView that is drag enabled with the shell but I'm unsure where to begin.
So that's the end of my exploration of Delphi's shell controls. If anyone knows how to drag from Delphi to other applications or can suggest other improvements to my frame please contact me. The source for my frame and test program can be downloaded, feel free to use it in your own programs.

Figure 2 - Demo Program
unit ExplorerFrame;
{Frame that mimics basic Windows Explorer functionality using Borland's Shell Controls TShellTreeView and TShellListView. }
{(c) 2003 Conspiracy Software. Written by Dave Murray, May/June 2003.}
{To enable files to be dropped from Explorer, OnCreate event of form that contains this frame must call:
DragAcceptFiles(TconExplorerFrame1.Handle, true); }
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs,
ImgList, ToolWin, ComCtrls, ShellCtrls, ExtCtrls, Menus,
StdCtrls;
type
TconExplorerFrame = class(TFrame)
pnlLeft: TPanel;
pnlButtons: TPanel;
tlbrButtons: TToolBar;
tlbtnUp:
TToolButton;
tlbtnRefresh:
TToolButton;
tlbtnSplit1:
TToolButton;
tlbtnCreateFolder: TToolButton;
tlbtnDelete:
TToolButton;
tlbtnSplit2:
TToolButton;
tlbtnProperties:
TToolButton;
tlbtnViews:
TToolButton;
pnlFoldersHeader: TPanel;
shlltrvwFolders: TShellTreeView;
splttrMiddle: TSplitter;
pnlRight: TPanel;
shlllstvwFiles: TShellListView;
pmnuViews: TPopupMenu;
pmLargeIcons: TMenuItem;
pmSmallIcons: TMenuItem;
pmList: TMenuItem;
pmDetails: TMenuItem;
imglstButtonsHot: TImageList;
imglstButtonsNorm: TImageList;
imglstButtonsDisabled: TImageList;
imglstViews: TImageList;
{### tlbrButtons BUTTON METHODS ###}
procedure tlbtnUpClick(Sender: TObject);
procedure tlbtnRefreshClick(Sender: TObject);
procedure tlbtnCreateFolderClick(Sender: TObject);
procedure tlbtnDeleteClick(Sender: TObject);
procedure tlbtnPropertiesClick(Sender: TObject);
{### pmnuViews POPUP MENU METHODS ###}
procedure pmLargeIconsClick(Sender: TObject);
procedure pmSmallIconsClick(Sender: TObject);
procedure pmListClick(Sender: TObject);
procedure pmDetailsClick(Sender: TObject);
{### DRAG + DROP METHODS ###}
procedure shlllstvwFilesStartDrag(Sender: TObject;
var DragObject: TDragObject);
procedure shlllstvwFilesEndDrag(Sender, Target:
TObject; X, Y: Integer);
procedure shlltrvwFoldersStartDrag(Sender: TObject;
var DragObject: TDragObject);
procedure shlltrvwFoldersEndDrag(Sender, Target:
TObject; X, Y: Integer);
procedure shlltrvwFoldersDragOver(Sender, Source:
TObject; X, Y: Integer; State: TDragState; var Accept: Boolean);
procedure shlltrvwFoldersDragDrop(Sender, Source:
TObject; X, Y: Integer);
{### Other METHODS ###}
procedure shlllstvwFilesDblClick(Sender: TObject);
private
{ Private declarations }
function FileOperation(const source, dest : string;
op, flags : Integer) : boolean;
function GetNewFolderName(const Path, FolderName :
string): string;
procedure DoContextMenuVerb(AFolder: TShellFolder;
Verb: PChar);
procedure WMDROPFILES(var Message:
TWMDROPFILES);message WM_DROPFILES;
public
{ Public declarations }
procedure Refresh;
end;
implementation
{$R *.dfm}
uses
ShellAPI, ShlObj, JclSysInfo;
const
cmvProperties : PChar = 'properties';
var
DragSourceFile : string;
{### tlbrButtons BUTTON METHODS ###}
procedure TconExplorerFrame.tlbtnUpClick(Sender: TObject);
{move up a dir}
begin
shlllstvwFiles.Back;
end; {procedure TfrmExplorerFrame.tlbtnUpClick}
procedure TconExplorerFrame.tlbtnRefreshClick(Sender: TObject);
{refresh shell displays}
begin
Refresh;
end; {procedure TfrmExplorerFrame.tlbtnRefreshClick}
procedure TconExplorerFrame.tlbtnCreateFolderClick(Sender: TObject);
{creates a new folder in current folder}
var
CurrentFolder, NewFolder : string;
begin
CurrentFolder := shlltrvwFolders.SelectedFolder.PathName + '\';
{ensure valid current folder}
if (CurrentFolder = 'Control Panel') or (CurrentFolder =
'Recycle Bin')
or (Length(CurrentFolder) = 0) or (Pos('nethood',
CurrentFolder) > 0) then Exit;
{get unique version of 'New Folder'}
NewFolder := GetNewFolderName(CurrentFolder, 'New Folder');
{create dir}
if not(CreateDir(NewFolder)) then begin
MessageDlg('ERROR: Unable to create folder!',
mtError, [mbOk], 0);
Exit;
end; {if not(CreateDir..}
Refresh;
end; {procedure TfrmExplorerFrame.tlbtnCreateFolderClick}
procedure TconExplorerFrame.tlbtnDeleteClick(Sender: TObject);
{deletes file or folder selected in shlltrvwFolders or shlllstvwFiles}
var
FileOrFolder : string;
OpSuccess : boolean;
begin
FileOrFolder := '';
{get selected file or folder}
if (shlltrvwFolders.Focused) then begin
FileOrFolder :=
shlltrvwFolders.SelectedFolder.PathName;
shlllstvwFiles.Back;
end {if shlltrvwFolders.Focussed..}
else if (shlllstvwFiles.Focused) then FileOrFolder :=
shlllstvwFiles.SelectedFolder.PathName;
{ensure valid file or folder}
if (FileOrFolder = 'Control Panel') or (FileOrFolder = 'Recycle
Bin')
or (Length(FileOrFolder) = 0) or (Pos('nethood',
FileOrFolder) > 0) then Exit;
{delete selected file or folder}
OpSuccess := FileOperation(FileOrFolder, '', FO_DELETE,
FOF_ALLOWUNDO);
if (OpSuccess) then Refresh;
end; {procedure TfrmExplorerFrame.tlbtnDeleteClick}
procedure TconExplorerFrame.tlbtnPropertiesClick(Sender: TObject);
{shows properties dialog for selected file/folder}
begin
if (shlltrvwFolders.Focused) then
DoContextMenuVerb(shlltrvwFolders.SelectedFolder, cmvProperties)
else if (shlllstvwFiles.Focused) then
DoContextMenuVerb(shlllstvwFiles.SelectedFolder, cmvProperties);
end; {procedure TconExplorerFrame.tlbtnPropertiesClick}
{### pmnuViews POPUP MENU METHODS ###}
procedure TconExplorerFrame.pmLargeIconsClick(Sender: TObject);
{popup menu item, selects Large Icon view}
begin
shlllstvwFiles.ViewStyle := vsIcon;
end; {procedure TfrmExplorerFrame.pmLargeIconsClick}
procedure TconExplorerFrame.pmSmallIconsClick(Sender: TObject);
{popup menu item, selects Small Icon view}
begin
shlllstvwFiles.ViewStyle := vsSmallIcon;
end; {procedure TfrmExplorerFrame.pmSmallIconsClick}
procedure TconExplorerFrame.pmListClick(Sender: TObject);
{popup menu item, selects List view}
begin
shlllstvwFiles.ViewStyle := vsList;
end; {procedure TfrmExplorerFrame.pmListClick}
procedure TconExplorerFrame.pmDetailsClick(Sender: TObject);
{popup menu item, selects Details view}
begin
shlllstvwFiles.ViewStyle := vsReport;
end; {procedure TfrmExplorerFrame.pmDetailsClick}
{### DRAG + DROP METHODS ###}
procedure TconExplorerFrame.shlllstvwFilesStartDrag(Sender: TObject;
var DragObject: TDragObject);
{store name of file/folder being dragged in DragSourceFile var global
to frame}
begin
DragSourceFile := (Sender as
TShellListView).SelectedFolder.PathName;
end; {procedure TconExplorerFrame.shlllstvwFilesStartDrag}
procedure TconExplorerFrame.shlllstvwFilesEndDrag(Sender, Target:
TObject; X, Y: Integer);
{clear DragSourceFile now nothing being dragged}
begin
DragSourceFile := '';
end; {procedure TconExplorerFrame.shlllstvwFilesEndDrag}
procedure TconExplorerFrame.shlltrvwFoldersStartDrag(Sender: TObject;
var DragObject: TDragObject);
{store name of folder being dragged in DragSourceFile var global to
frame}
begin
DragSourceFile := (Sender as
TShellTreeView).SelectedFolder.PathName;
end; {procedure TconExplorerFrame.shlltrvwFoldersStartDrag}
procedure TconExplorerFrame.shlltrvwFoldersEndDrag(Sender, Target:
TObject; X, Y: Integer);
{clear DragSourceFile now nothing being dragged}
begin
DragSourceFile := '';
end; {procedure TconExplorerFrame.shlltrvwFoldersEndDrag}
procedure TconExplorerFrame.shlltrvwFoldersDragOver(Sender, Source:
TObject; X, Y: Integer; State: TDragState; var Accept: Boolean);
{decides what dropped items shlltrvwFolders accepts}
begin
Accept := ((Source is TShellListView) or (Source is
TShellTreeView))
and not(shlltrvwFolders.DropTarget = nil);
end; {procedure TconExplorerFrame.shlltrvwFoldersDragOver}
procedure TconExplorerFrame.shlltrvwFoldersDragDrop(Sender, Source:
TObject; X, Y: Integer);
{accepts file/folder dropped on shlltrvwFolders, copy/move it to target folder}
var
MyListView : TCustomShellListView;
CurrentFolder : TTreeNode;
TargetFolder : string;
OpSuccess : boolean;
DragOp : integer;
begin
OpSuccess := false;
{determine copy or move}
if (GetKeyState(VK_CONTROL)) then DragOp := FO_COPY
else DragOp := FO_MOVE;
if ((Source is TShellListView) or (Source is TShellTreeView)) then begin
{copy/move file DragSourceFile to DropTarget folder}
{ensure source valid}
if (DragSourceFile = 'Control Panel') or (DragSourceFile = 'Recycle Bin')
or (Pos('nethood', DragSourceFile) > 0) or (Length(DragSourceFile) = 0) then Exit;
{identify target folder using Select to get a TShellFolder}
{TShellListView has flickers when TShellTreeView changes so detatch first}
MyListView := shlltrvwFolders.ShellListView;
shlltrvwFolders.ShellListView := nil;
{select target folder}
CurrentFolder := shlltrvwFolders.Selected;
shlltrvwFolders.Select(shlltrvwFolders.DropTarget);
{get full path of target folder}
TargetFolder := shlltrvwFolders.SelectedFolder.PathName;
{re-select previous folder}
shlltrvwFolders.Select(CurrentFolder);
{re-atatch TShellListView}
shlltrvwFolders.ShellListView := MyListView;
{ensure target folder valid}
if (TargetFolder = 'Control Panel') or (TargetFolder = 'Recycle Bin')
or (Pos('nethood', TargetFolder) > 0) or (Length(TargetFolder) = 0) then Exit;
{copy/move source file/folder to target folder}
OpSuccess := FileOperation(DragSourceFile, TargetFolder, DragOp, FOF_ALLOWUNDO);
end; {if (Source is TShellListView..}
if (OpSuccess) then begin
{refresh controls if successfult}
if ((Source is TShellTreeView) and (DragOp = FO_MOVE)) then
shlllstvwFiles.Back; {folder displayed has been moved so go up}
Refresh;
end; {if OpSuccess..}
end; {procedure TconExplorerFrame.shlltrvwFoldersDragDrop}
{### Other METHODS ###}
procedure TconExplorerFrame.shlllstvwFilesDblClick(Sender: TObject);
{Do default action when file double-clicked,
because TCustomShellListView.DblClick doesn't do this correctly}
begin
{ensure something selected}
if (shlllstvwFiles.SelectedFolder = nil) then Exit;
{ignore if a folder}
if (shlllstvwFiles.SelectedFolder.IsFolder) then Exit;
{do default action}
shlllstvwFiles.SelectedFolder.ExecuteDefault;
end; {procedure TconExplorerFrame.shlllstvwFilesDblClick}
{### PRIVATE METHODS ###}
function TconExplorerFrame.FileOperation(const source, dest : string;
op, flags : Integer) : boolean;
{perform Copy, Move, Delete, Rename on files + folders via WinAPI}
var
Structure : TSHFileOpStruct;
src, dst : string;
OpResult : integer;
begin
{setup file op structure}
FillChar(Structure, SizeOf (Structure), #0);
src := source + #0#0;
dst := dest + #0#0;
Structure.Wnd := 0;
Structure.wFunc := op;
Structure.pFrom := PChar(src);
Structure.pTo := PChar(dst);
Structure.fFlags := flags;
case op of
{set title for simple progress dialog}
FO_COPY : Structure.lpszProgressTitle := 'Copying...';
FO_DELETE : Structure.lpszProgressTitle := 'Deleting...';
FO_MOVE : Structure.lpszProgressTitle := 'Moving...';
FO_RENAME : Structure.lpszProgressTitle := 'Renaming...';
end; {case op of..}
OpResult := 1;
try
{perform operation}
OpResult := SHFileOperation(Structure);
finally
{report success / failure}
result := (OpResult = 0);
end; {try..finally..}
end; {function TconExplorerFrame.FileOperation}
function TconExplorerFrame.GetNewFolderName(const Path, FolderName : string): string;
{returns a unique name for a new folder with a number if necessary
parameter Path must end with \}
var
i : integer;
begin
result := Path + FolderName;
i := 0;
{check if dir exists and if so add a number and try again}
while DirectoryExists(result) do begin
inc(i);
result := Path + FolderName + ' ' + IntToStr(i);
end; {while..}
end; {function TconExplorerFrame.GetNewFolderName}
procedure TconExplorerFrame.DoContextMenuVerb(AFolder: TShellFolder; Verb: PChar);
{executes a command from an item's context menu, currently used for Properties button}
var
ICI: TCMInvokeCommandInfo;
CM: IContextMenu;
PIDL: PItemIDList;
begin
if AFolder = nil then Exit;
FillChar(ICI, SizeOf(ICI), #0);
with ICI do begin
cbSize := SizeOf(ICI);
fMask := CMIC_MASK_ASYNCOK;
hWND := 0;
lpVerb := Verb;
nShow := SW_SHOWNORMAL;
end; {with ICI..}
PIDL := AFolder.RelativeID;
AFolder.ParentShellFolder.GetUIObjectOf(0, 1, PIDL, IID_IContextMenu, nil, CM);
CM.InvokeCommand(ICI);
end; {procedure TconExplorerFrame.DoContextMenuVerb}
procedure TconExplorerFrame.WMDROPFILES(var Message: TWMDROPFILES);
{accepts files dropped from shell (eg. Explorer), copies to
shlllstFiles}
var
i, NoOfItems, size: integer;
DragFile : PChar;
TargetFolder : string;
OpSuccess : boolean;
begin
inherited;
OpSuccess := false;
DragFile := StrAlloc(255);
{get + validate target folder}
TargetFolder := shlllstvwFiles.RootFolder.PathName;
if (TargetFolder = 'Control Panel') or (TargetFolder = 'Recycle Bin')
or (Pos('nethood', TargetFolder) > 0) or
(Length(TargetFolder) = 0) then Exit;
{get no of source items}
NoOfItems := DragQueryFile(Message.Drop, $FFFFFFFF, DragFile, 255);
{iterate thru items}
for i := 0 to (NoOfItems - 1) do begin
{get size of item's full path and allocate PChar}
size := DragQueryFile(Message.Drop, i , nil, 0) + 1;
DragFile := StrAlloc(size);
{get full path of item}
DragQueryFile(Message.Drop,i , DragFile, size);
{copy/move source file/folder to target folder}
OpSuccess := FileOperation(DragFile, TargetFolder, FO_COPY, FOF_ALLOWUNDO);
StrDispose(DragFile);
end; {for i..}
DragFinish(Message.Drop);
if (OpSuccess) then Refresh;
end; {procedure TconExplorerFrame.WMDROPFILES}
{### PUBLIC METHODS ###}
procedure TconExplorerFrame.Refresh;
{refresh both shell views}
var
MyListView : TCustomShellListView;
begin
{TShellListView has flickers when TShellTreeView changes so detach first}
MyListView := shlltrvwFolders.ShellListView;
shlltrvwFolders.ShellListView := nil;
shlltrvwFolders.Refresh(shlltrvwFolders.Items.GetFirstNode);
{Re-attach TShellListView}
shlltrvwFolders.ShellListView := MyListView;
shlllstvwFiles.Refresh;
end; {procedure TconExplorerFrame.Refresh}
initialization
{initialise vars global to frame}
DragSourceFile := '';
end.
|
Download this article in eBook format. You can download a free eBook reader for PalmOS, PocketPC, MacOS, Symbian or Windows. |
|
Download the code with a Demo application. |