Title: Using the IShellFolder interface
Question: The IShellFolder interface is used to enumerate and manipulate the contents of a folder
Answer:
To enumerate the object of the Windows' namespace, we have to use the IShellFolder
interface. Each container object (usually folders) must implement this interface.
Here is IShellFolder declaration (ShlObj.pas):
IShellFolder = interface(IUnknown)
[SID_IShellFolder]
function ParseDisplayName(hwndOwner: HWND;
pbcReserved: Pointer; lpszDisplayName: POLESTR; out pchEaten: ULONG;
out ppidl: PItemIDList; var dwAttributes: ULONG): HResult; stdcall;
function EnumObjects(hwndOwner: HWND; grfFlags: DWORD;
out EnumIDList: IEnumIDList): HResult; stdcall;
function BindToObject(pidl: PItemIDList; pbcReserved: Pointer;
const riid: TIID; out ppvOut): HResult; stdcall;
function BindToStorage(pidl: PItemIDList; pbcReserved: Pointer;
const riid: TIID; out ppvObj): HResult; stdcall;
function CompareIDs(lParam: LPARAM;
pidl1, pidl2: PItemIDList): HResult; stdcall;
function CreateViewObject(hwndOwner: HWND; const riid: TIID;
out ppvOut): HResult; stdcall;
function GetAttributesOf(cidl: UINT; var apidl: PItemIDList;
var rgfInOut: UINT): HResult; stdcall;
function GetUIObjectOf(hwndOwner: HWND; cidl: UINT; var apidl: PItemIDList;
const riid: TIID; prgfInOut: Pointer; out ppvOut): HResult; stdcall;
function GetDisplayNameOf(pidl: PItemIDList; uFlags: DWORD;
var lpName: TStrRet): HResult; stdcall;
function SetNameOf(hwndOwner: HWND; pidl: PItemIDList; lpszName: POLEStr;
uFlags: DWORD; var ppidlOut: PItemIDList): HResult; stdcall;
end;
The key methods are EnumObjects() and BindToObject(). The first is used to enumerate objcets within the current folder, the latter to obtain the IShellFolder interface
of a contained object.
EnumObjects() returns an IEnumIDList interface that is used to iterate all over the objects. You can control what objects are enumerated using the grfFlags parameters, which can have any combination of yhis values:
SHCONTF_FOLDERS = 32, // for shell browser
SHCONTF_NONFOLDERS = 64, // for default view
SHCONTF_INCLUDEHIDDEN = 128, // for hidden or system objects
IEnumIDList has the following declaration:
IEnumIDList = interface(IUnknown)
[SID_IEnumIDList]
function Next(celt: ULONG; out rgelt: PItemIDList;
var pceltFetched: ULONG): HResult; stdcall;
function Skip(celt: ULONG): HResult; stdcall;
function Reset: HResult; stdcall;
function Clone(out ppenum: IEnumIDList): HResult; stdcall;
end;
Next() returns an array of PIDLs (pointer to item identifier list), up to "celt".
"pceltFetched" holds the number of PIDLs retieved. Skip() moves "celt" objects forward,
and Reset() returns to the beginning of the enumeration. With Clone() you can get
a copy of the IEnumIDList interface with the same state of the current, useful, for
example, if you wish to put a "bookmark" while enumearting.
Here is an example to enumerate all the objects on the Desktop:
...
var
Desktop: IShellFolder
EnumIDLIst: IEnumIDList;
CurPidl: PItemIDLIst;
Fetched: ULONG;
begin
if SHGetDesktopFolder(Desktop) NOERROR then
begin
if Desktop.EnumObjects(Handle, SHCONTF_FOLDERS, EnumIDList) = NOERROR then
begin
// Retrieve objects one at a time...
while EnumIDList.Next(1, CurPidl, Fetched) = S_OK do
begin
... // Add code here to do something with the objects
CoTaskMemFree(CurPidl); // Remember to free PIDLs!
end;
end;
end;
end;
To enumerate a subfolder content, we have to retrieve its IShellFodler interface. If ParentFolder contains already an IShellFolder interface, and ChildPidl contains a valid subfolder PIDL (returned by IEnumIDList.Next(), for example), we can write
var
ChildFolder: IShellFolder;
...
if ParentFolder.BindToObject(ChildPidl, nil, IID_IShellFolder, pointer(ChildFolder)) NOERROR then
begin
with ChildFolder do
begin
// Do something with the new IShellFolder interface...
end;
end;
The other methods of IShellFolder are used to manipulate the objects:
ParseDisplayName() is used to translate a display name (i.e. a path) into an item
identifier list.
CompareIDs() determines the relative ordering of two file objects or folders,
given their item identifier lists.
GetAttributeOf() retrieves the attributes of one or more file objects or subfolders.
Here are a list of possible values:
SFGAO_CANCOPY The specified file objects or folders can be copied.
SFGAO_CANDELETE The specified file objects or folders can be deleted.
SFGAO_CANLINK It is possible to create shortcuts for the specified file objects or folders.
SFGAO_CANMOVE The specified file objects or folders can be moved.
SFGAO_CANRENAME The specified file objects or folders can be renamed.
SFGAO_CAPABILITYMASK Mask for the capability flags.
SFGAO_DROPTARGET The specified file objects or folders are drop targets.
SFGAO_HASPROPSHEET The specified file objects or folders have property sheets.
SFGAO_DISPLAYATTRMASK Mask for the display attributes.
SFGAO_GHOSTED The specified file objects or folders should be displayed using a ghosted icon.
SFGAO_LINK The specified file objects are shortcuts.
SFGAO_READONLY The specified file objects or folders are read-only.
SFGAO_SHARE The specified folders are shared.
SFGAO_CONTENTSMASK Mask for the contents attributes.
SFGAO_HASSUBFOLDER The specified folders have subfolders (and are, therefore, expandable in the left pane of Windows Explorer).
SFGAO_FILESYSTEM The specified folders or file objects are part of the file system (that is, they are files, directories, or root directories).
SFGAO_FILESYSANCESTOR The specified folders contain one or more file system folders.
SFGAO_FOLDER The specified items are folders.
SFGAO_REMOVABLE The specified file objects or folders are on removable media.
For example, we can use GetAttributeOf to select appropriate icons.
GetDisplayNameOf() and SetName() are used to retrieve and set the object name.
Note that the display name is returned in a strange structure:
_STRRET = record
uType: UINT; { One of the STRRET_* values }
case Integer of
0: (pOleStr: LPWSTR); { must be freed by caller }
1: (pStr: LPSTR); { NOT USED }
2: (uOffset: UINT); { Offset into SHITEMID (ANSI) }
3: (cStr: array[0..MAX_PATH-1] of Char); { Buffer to fill in }
end;
uType tells us what field holds the name. Remember that pOleStr must be freed by the caller!