The core of any namespace extension is a ActiveX Library
project, main TSxModule and main TPermanentModule. This modules
created using New... command from Delphi's File
menu.
MainModule represents interface
between your NSE and Windows Shell. It's instance created almost
for every call from Windows Shell and you will try to make its implementation
so "light" as possible.
PermanentModule represents your
NSE DataModule. It created once per Explorer process and will contain
implementation of all "heavy" and long-time operations.
Also you can learn Shell+ MegaDemo NSE
object-model, which were produced using Model
Maker:
Only three basic components required to create simple
namespace extension: ShellFolder, ShellView and DataProvider. There
are several kins of each component available, you can use any combinations
of them depending on what you need. Also, feel free to contact
us if you need custom data providers for any thirdparty data
types or custom shell views.
As ShellFolder component in this example we will use
TSxVirtualFolder. This component allows your namespace
extension to be installed as virtual folder like My Documents, Control
Panel, Network Places and so on. Our namespace extension will be
installed under the My Computer folder. We have
to set some important properties for this component:
Active = True
CLSID = any unique GUID
URLIdentifier = [test]
WantsForParsing
True
As ShellView we'll use several
different components, but on the first time we'll put the TSxSysShellView
on the MainModule and link it with ShellFolder.ShellView
property. There are no important properties on the first time for
this component.
This demo will work with custom data format, and we
will use Microsoft Compound File Implementation
to store its data. Data provider to work with this data format is
not implemented, but it's not a problem with TSxByHandProvider
component. It allows you to handle any data format with your own
algorithm.
Put the TSxByHandProvider
on the PermanentModule and add Permanent module's
unit name to uses section of MainModule unit. There
are many important properties and events for DataProvider component,
but we will specify them later.
Connect newly created PermanentModule.DataProvider
to MainModule.VirtualFolder.DataProvider property. This is
the most important relation in the NSE - connecting Windows Shell
interface with the data.
Additional stuff
To make the namespace extention more user-friendly
we have to specify the icon and property page for the root folder.
Use TSxExtractIcon and TSxShellPropSheetExt components to do it.
This components correspondingly linked to ShellFolder's properties
ExtractIconHandler and PropertySheetHandler.
TSxExtractIcon contains only static icons to
our namespace extensions, but you can show any icon dynamically
depending on your NSE's state (like Service is online or
Service is offline).
Root property page will be used to choose database
file to work with. To implement the root property sheet we have
to create new property sheet page (using New...
command from Delphi's File menu) and link it to
the PropertySheet component. Read more about handling Property Page
events in the Property
Sheet development guide.
The Data Storage
The DataProvider component works like mediator between
your namespace extension and Windows Shell. You can read more about
data provider and its events in the article "Writing
data handling events for DataProvider". In this project
we providing folders structure to data provider using the OnPopulate
event.
To make things much closer to reality our database
implemented as independed server. We have one connection to storage
file and this file can be specified using Properies dialog for our
root folder. MDServer is a property to provide access to
remote COM server.
There is one more reason to use independent server
to keep and handle data: your namespace extension works inside of
different applications and "eat" system resources. So
it should be a "thin client" which can only display data
and handle user input.
This demo interact with its server through COM interfaces.
This is one of possible approaches. The often used thirdparty communication
solutions are: RemObjects,
MsgConnect,
Indy,
Synapse
Sockets.
The code to populate our virtual items will look like:
procedure TPermanentModule.DataProviderPopulate(Sender: TSxByHandProvider;
DataGrain: TSxByHandDataGrain; GrainList: TList);
var
tempGrain:TSxCustomDataGrain;
grainString:String;
stgFileObj:IMDFileObj;
i:Integer;
tempStr:TStrings;
tempWideString:WideString;
tempStgObj:IMDFileObj;
begin
if not LoggedIn then
begin
if not DoLogin then
Exit;
end
else
SetLogoutTime;
if DataGrain.IsRoot then // DataGrain is a folder, that need to be populated
begin
// Creating virtual folder (it will display files)
tempGrain:=Sender.CreateGrainInstance;
tempGrain.Name:='Stored Files';
tempGrain.GrainType:=gtFolder;
tempGrain.Strings.Values[vGrainPath]:=folderID_Files;
tempGrain.Strings.Values[vSysIconOnly]:='1';
GrainList.Add(tempGrain); // adding newly created item to the list
end
else
begin
grainString:=DataGrain.Strings.Values[vGrainPath]; // Enumerated grain path
stgFileObj:=nil;
if grainString=folderID_Files then
begin
if Failed(MDServer.GetFileStorage(stgFileObj)) then // Displaying files
Exit;
end
else
if grainString=folderID_TreeView then // Displaying file tree view
begin
Exit;
end
else
if grainString=folderID_Grid then // Displaying data in grid
begin
Exit;
end
else
begin // working with some file grain (the string is a path)
if Failed(MDServer.GetRelativeFileStorage(grainString,stgFileObj)) then // Displaying files
Exit;
end;
if stgFileObj=nil then
Exit;
tempStr:=TStringList.Create;
try
stgFileObj.GetChildNames(tempWideString); // retreiving the list of childs
tempStr.Text:=tempWideString; // to make things easy...
for i:=0 to tempStr.Count-1 do
begin
stgFileObj.GetChildByName(tempStr[i],tempStgObj); // retreiving child's object
if tempStgObj=nil then
Continue;
tempGrain:=CreateGrainFromStg(tempStgObj,DataGrain);
GrainList.Add(tempGrain);
end;
finally
FreeAndNil(tempStr);
end;
end;
end;
It is only complicated method in the Namespace extension
written with Shell+. As incoming parameter it receive DataGrain.
You have to fetch all underlying DataGrains and add them to the
GrainList param.
This event called every time when Windows Shell or
any other application trying to get contents of any of your virtual
folders, which specified by DataGrain param.
Implementing Login
dialog
As you can see, OnPopulate event call several methods
(LoggedIn, DoLogin) to check if user has logged in.
DoLogin method will display dialog to ask user for login
and password. Also you can use TSxVirtualFolder.OnGetShellView
event to switch to another shellview when users has not logged in.
For example you can switch to TSxShellFormView, which will display
login dialog embedded to Explorer. This is currently not implemented
in this demo.
function TPermanentModule.DoLogin: Boolean;
var
tempLoginForm:TLoginForm;
begin
Result:=False;
tempLoginForm:=TLoginForm.Create(Self);
try
if tempLoginForm.ShowModal=mrOk then
begin
LoggedIn:=True;
Result:=True;
end
else
DoLogout;
finally
FreeAndNil(tempLoginForm);
end;
end;
function TPermanentModule.GetLoggedIn: Boolean;
var
tempTime:TTimeStamp;
begin
Result:=FLoggedIn;
if Result=True then
begin
tempTime:=DateTimeToTimeStamp(Time);
Result:=tempTime.Date=LogoutTime.Date;
if Result then
Result:=tempTime.Time<=LogoutTime.Time;
FLoggedIn:=Result;
end;
end;
Implementing detail
columns
To provide information in detailed columns to Windows
Explorer we use TSxNamespaceDetails component. Put it on
the MainModule and connect it to SysShellView.Details
property.
Next step you have to specify the list of columns,
which will be displayed in your NSE. Add columns to SxNamespaceDetails.Columns
property. In our example it is three columns: Name, Size and Path.
Name and Size columns are commonly used columns, so
we will register them is additional properties:
Path is additional column, which can be displayed
by user by choosing Explorer menu View -> Choose Details...:
Clipboard and
Drag&Drop operations
To allow your NSE to support clipboard and Drag&Drop
operations you need to implement several DataProvider events. To
get more information about Data Handling events please refer to
article "Writing
data handling events for DataProvider".
When user drop folder into the your namespace extension,
DataProvider automatically parse incoming data structure and raise
appropriate events for dropped folder, its subfolders and files
inside.
This demo will show you how to create new DataGrains
from files and folders which were dropped to NSE folder.
To handle incoming files we creating two events: OnNewGrainFromFile
and OnNewGrainFromStream:
procedure TPermanentModule.DataProviderNewGrainFromFile(
Sender: TSxCustomProvider; FolderGrain: TSxCustomDataGrain;
FileName: WideString;var NewGrain:TSxCustomDataGrain);
var
tempFileStg:IMDFileObj;
newFileObj:IMDFileObj;
fileStream:TFileStream;
tempVar:OleVariant;
begin
// retreiving file container from database (Folder in this case)
MDServer.GetRelativeFileStorage(FolderGrain.Strings.Values[vGrainPath],tempFileStg);
if tempFileStg=nil then
Exit;
// adding new file
tempFileStg.AddNewChild(ExtractFileName(FileName),newFileObj);
if newFileObj=nil then
Exit;
// specifying file attributes
newFileObj.ReadOnly:=False;
newFileObj.IsSystemIcon:=True;
newFileObj.Kind:=okFile;
// loading data to database
fileStream:=TFileStream.Create(FileName,fmShareDenyNone or fmOpenRead);
try
tempVar:=SxStreamToVarArray(fileStream);
if not VarIsNull(tempVar) then
newFileObj.SetContents(tempVar);
finally
FreeAndNil(fileStream);
end;
// updating changes into database
newFileObj.UpdateChanges;
// updating ShellView
Sender.NotifyUpdateGrain(FolderGrain);
// returning representation of database object
NewGrain:=CreateGrainFromStg(newFileObj,FolderGrain);
end;
procedure TPermanentModule.DataProviderNewGrainFromStream(
Sender: TSxCustomProvider; FolderGrain: TSxCustomDataGrain;
FileInfo: TSxFileInfoDescriptor; FileData: TStream);
var
tempFileStg:IMDFileObj;
newFileObj:IMDFileObj;
tempVar:OleVariant;
begin
MDServer.GetRelativeFileStorage(FolderGrain.Strings.Values[vGrainPath],tempFileStg);
if tempFileStg=nil then
Exit;
tempFileStg.AddNewChild(FileInfo.FileName,newFileObj);
if newFileObj=nil then
Exit;
newFileObj.ReadOnly:=ftReadOnly in FileInfo.FileAttributes;
newFileObj.IsSystemIcon:=True;
newFileObj.Kind:=okFile;
tempVar:=SxStreamToVarArray(FileData);
newFileObj.SetContents(tempVar);
newFileObj.UpdateChanges;
Sender.NotifyUpdateGrain(FolderGrain);
end;
These events are almost similar: first of all this
event retrieve storage object by FolderGrain, then it save information
from file or stream to the server.
To handle incoming folders we writing event handler
for DataProvider.OnCreateNewFolder event:
procedure TPermanentModule.DataProviderCreateNewFolder(
Sender: TSxCustomProvider; OwnerFolderGrain: TSxCustomDataGrain;
NewFolderName: WideString; out NewFolderGrain: TSxCustomDataGrain);
var
tempFileStg:IMDFileObj;
tempNewStg:IMDFileObj;
begin
MDServer.GetRelativeFileStorage(OwnerFolderGrain.Strings.Values[vGrainPath],tempFileStg);
if tempFileStg=nil then
Exit;
tempFileStg.AddNewChild(NewFolderName,tempNewStg);
NewFolderGrain:=CreateGrainFromStg(tempNewStg,OwnerFolderGrain);
end;
To allow our virtual
files to be copied to another folders and applications we implementing
two events: OnGetGrainFileContents and OnGetGrainFileInfo:
procedure TPermanentModule.DataProviderGetGrainFileInfo(
Sender: TSxCustomProvider; DataGrain: TSxCustomDataGrain;
var FileInfo: TSxFileInfoDescriptor);
var
tempFileStg:IMDFileObj;
begin
MDServer.GetRelativeFileStorage(DataGrain.Strings.Values[vGrainPath],tempFileStg);
if tempFileStg=nil then
Exit;
FileInfo.FileAttributes:=DataGrain.Attributes;
FileInfo.CreationTime:=Time;
FileInfo.LastAccessTime:=Time;
FileInfo.LastWriteTime:=Time;
FileInfo.FileSize:=tempFileStg.Size;
FileInfo.FileName:=tempFileStg.Name;
end;
procedure TPermanentModule.DataProviderGetGrainFileContents(
Sender: TSxCustomProvider; DataGrain: TSxCustomDataGrain;
Stream: TMemoryStream);
var
tempFileStg:IMDFileObj;
tempVar:OleVariant;
begin
MDServer.GetRelativeFileStorage(DataGrain.Strings.Values[vGrainPath],tempFileStg);
if tempFileStg=nil then
Exit;
if Failed(tempFileStg.GetContents(tempVar)) then
Exit;
SxVarArrayToStream(tempVar,Stream);
end;
OnGetGrainFileInfo event used to provide system-related
information to filesystem or other application, and OnGetGrainFileContents
event used to specify virtual file contents.
When user drag folder outside from your namespace
extension DataProvider automatically handle folder information and
raise only file-related events.
System and custom
icons for files and folders inside virtual namespace
Our namespace extension will use common system icons
to display its files. It is very easy to implement since 2.3 version:
procedure TPermanentModule.DataProviderPrepareGrainApperance(
Sender: TSxCustomProvider; DataGrain: TSxCustomDataGrain;
var IconParams: TSxPrepareIconParams);
begin
IconParams.UseSystemIcon:=DataGrain.Strings.Values[vSysIconOnly]='1';
end;
But this demo allows to customize its virtual icons:
So you have to implement additional event, which load
customized icon from server and provide it to Windows Shell:
procedure TPermanentModule.DataProviderGetIcon(Sender: TSxCustomProvider;
DataGrain: TSxCustomDataGrain; var IconIndex, OpenIconIndex: Integer);
var
tempFileStg:IMDFileObj;
tempIcon:TIcon;
tempStream:TMemoryStream;
tempVar:OleVariant;
begin
if DataGrain.Strings.Values[vSysIconOnly]='1' then
begin
IconIndex:=SxGetFileSystemIcon(DataGrain.Name,False,DataGrain.GrainType=gtFolder);
if DataGrain.GrainType=gtFolder then
OpenIconIndex:=SxGetFileSystemIcon(DataGrain.Name,True,DataGrain.GrainType=gtFolder);
end
else
begin
IconIndex:=0;
OpenIconIndex:=0;
tempIcon:=TIcon.Create;
tempStream:=TMemoryStream.Create;
try
MDServer.GetRelativeFileStorage(DataGrain.Strings.Values[vGrainPath],tempFileStg);
if tempFileStg=nil then
Exit;
// retreiving normal icon image and adding it to system image list
tempFileStg.GetIcon(tempVar);
SxVarArrayToStream(tempVar,tempStream);
tempStream.Position:=0;
IconIndex:=DataProvider.SmallIcons.AddIconFromStream(tempStream);
tempStream.Position:=0; // large icons index is the same as small icon index
DataProvider.LargeIcons.AddIconFromStream(tempStream);
// retreiving open icon image from database and adding it to system image list
tempStream.Position:=0;
tempFileStg.GetOpenIcon(tempVar);
SxVarArrayToStream(tempVar,tempStream);
tempStream.Position:=0;
OpenIconIndex:=DataProvider.LargeIcons.AddIconFromStream(tempStream);
tempStream.Position:=0; // large icons index is the same as small icon index
DataProvider.LargeIcons.AddIconFromStream(tempStream);finally
FreeAndNil(tempIcon);
FreeAndNil(tempStream);
end;
end;
end;
Displaying overlays
for icons in the NSE
To display overlay icons for your virtual files you
have to put two TSxIconList components on the permanent module and
connect them to DataProvider.LargeOverlayIcons and DataProvider.SmallOverlayIcons.
These two lists will contain one and same icon in small and large
sizes.
Then you need just implement one event handler and
your NSE will display overlay icon depending on file state:
procedure TPermanentModule.DataProviderGetOverlayIndex(
Sender: TSxCustomProvider; DataGrain: TSxCustomDataGrain;
var OverlayIndex: Integer; var DisplayOverlay: Boolean);
begin
OverlayIndex:=0;
if ftReadOnly in DataGrain.Attributes then
DisplayOverlay:=True
else
DisplayOverlay:=False;
end;
Property pages for
virtual files and folders
TSxNamespacePropertySheet component used to
add property pages to the NSE. Put this component on PermanentModule
and connect it to the DataProvider.PropertySheets property.
Then you have to create new property page form (using New...
command from Delphi's File menu) and link it to
the TSxNamespacePropertySheet component (PropertySheets
property).
From property page form you can use any available
methods of PermanentModule, for example - this is method,
which read current DataGrain settings from server using PermanentModule
properties:
procedure TGrainPropSheetForm.LoadProperties;
var
tempFileStg:IMDFileObj;
tempGrain:TSxCustomDataGrain;
tempServer:IMDServer;
begin
tempServer:=PermanentModule.MDServer;
tempGrain:=TSxNamespacePropertySheet(PropSheetComponent).DataGrain;
tempServer.GetRelativeFileStorage(tempGrain.Strings.Values[vGrainPath],tempFileStg);
if tempFileStg=nil then
Exit;
edObjName.Text:=tempFileStg.Name;
edtObjsCount.Text:=IntToStr(tempFileStg.ChildCount);
edtSize.Text:=SxFormatSizeStr(IntToStr(tempFileStg.Size div 1024));
cbReadOnly.Checked:=tempFileStg.ReadOnly;
tempGrain.GetCurrentIcon(imgIcon.Picture.Icon,is32x32,False);
if tempFileStg.IsSystemIcon then
begin
btnUseSystem.Enabled:=False;
btnChangeIcon.Enabled:=True;
end
else
begin
btnUseSystem.Enabled:=True;
btnChangeIcon.Enabled:=False;
end;
end;
PropSheetComponent
is a property of TSxShellPropSheetForm. This event handler
retrieve current DataGrain and load its data from server.
When
user click Apply or Ok button, event FileObjPropSheet.OnApply
or OnApplyEx will be called. By handling this event you have
to store modified data from property page to server:
procedure TPermanentModule.FileObjPropSheetApplyEx(
Sender: TSxShellPropSheetExt; PropSheet: TForm;
var ApplyChanges: Boolean);
begin
TGrainPropSheetForm(PropSheet).SaveProperties;
ApplyChanges:=True;
end;
Context menus support
DataProvider component have several properties
and events to implement flexible context menus for Namespace extensions.
Put the TSxGrainMenu component on the PermanentModule
and connect it to DataProvider.FolderContextMenu property. This
menu will be displayed only for folders in your Namespace extension.
You can control what menu to display for specific DataGrain by handling
DataProvider.OnGetFileContextMenu and DataProvider.OnGetFolderContextMenu
events.
Common menu items like Cut, Copy, Paste, Delete and
Rename will be added automatically when you implement data handling
events for DataProvider component.
Explorer toolbar
support
Drop the TSxExplorerToolbar component on the
MainModule and two TImageList components on permanent module. These
imagelists will contain large and small button images.
Then you will add one or more buttons to the ExplorerToolbar.Buttons
property. Each button will be linked with TMenuItem on the PermanentModule.
Also you can choose between one of common toolbar
images or any of your custom using Image and ImageIndex
properties for each button.
Main Explorer menu support
Main Explorer menu extended using TSxExplorerMenu
component. As like as with toolbar, you have to drop the TSxExplorerMenu
component on the MainModule and specify any number of menu
items. Currently you can extend only the following Explorer menus:
File, Edit, View, Tools and Help.
Each menu item, which were added to TSxExplorerMenu
component will be connected with TMenuItem on the PermanentModule.
You can use one and the same TMenuItem with Explorer menu item and
Toolbar item.
This demo extends File and Tools menu items of Windows
Explorer:
Compile MegaDemo server and MegaDemo NSE projects.
Register MegaDemo NSE using command "regsvr32.exe
ExMegaDemoNSE.dll"
Register MegaDemo server using command "ExMegaDemoServer.exe
-regserver"
Open Windows Explorer and choose item Properties
from "MegaDemo Virtual Folder" item, which will appear
under the "My Computer" node.
Click on the button "Create New Database"
and specify file name for the new empty database. Also you can
download sample MegaDemo database and choose it using "Browse..."
button.
After you click Ok in the Properties dialog of
MegaDemo NSE you will be able to work with this demo.