Introduction
As a long time reader, I've been wondering for some time now how to contribute myself and needed a good idea.
Last week, on one of the (French) newsgroups that I usually read, somebody asked how to know if a file
has been deleted by a user, and if so if the file is always in the Recycle Bin.
By reading this question and the only two answers which were given to the asker, a good question
arose: how does this stuff (the Recycle Bin) work?
File Operation
If you look in the MSDN, you'll find that to delete a file, you'll have several options:
- Using the File API by calling
DeleteFile
- Using the
SHFileOperation
and filling accordingly the FILEOPSTRUCT
parameter
The first method is really the hard way: when you call it the object vanishes
- deleted for ever!
The second method is slightly different: you must use the FO_DELETE
opcode and have the opportunity to use
the flag FOF_ALLOWUNDO
. If you don't use this flag, then the operation will be similar to what would have been done by using the
DeleteFile
API. Doing it the other way (using the flag), the file is moved to
a hidden folder whose name will vary depending on the format
of the hard-drive: if it's NTFS, the name will be Recycler, otherwise Recycled.
During this operation, many things are done by the system :
- The file doesn't move on the disk, only it's directory entry. Just like we have issued a
MoveFile
or have used the FO_MOVE
flag. - In the Recycle Bin, the filename is changed and some information is stored in a proprietary index (if interested on this topic, read this article from the MSKB))
Basically, these two operations are why we found a Recycled/Recycler folder on each of the physical drives in our computers.
Now, if you have a look at your Explorer, you'll find a unique interface : Recycle Bin (if the name hasn't been changed).
Shell functioning
Those of you who have used the Shell namespace will know that this is a virtual folder which must be accessed by using its
IShellFolder
interface.
For the moment, this is fairly simple:
LPSHELLFOLDER pDesktop = NULL;
LPITEMIDLIST pidlRecycleBin = NULL;
HRETURN hr = S_OK;
hr = SHGetDesktopFolder(&pDesktop);
hr = SHGetSpecialFolderLocation (m_hWnd, CSIDL_BITBUCKET, &pidlRecycleBin);
hr = pDesktop->BindToObject(pidlRecycleBin, NULL,
IID_IShellFolder, (LPVOID *)&m_pRecycleBin);
When we have this interface to the Recycle Bin, we can obtain it's real name (the one shown in the Explorer if you'll have changed it),
and it's what is used by the application to change its window-title:
STRRET strRet;
hr = pDesktop->GetDisplayNameOf (pidlRecycleBin,
SHGDN_NORMAL, &strRet);
The
STRRET
datatype is a structure which is able to hold a string from formats varying from
ansi, OLE string or offset in a buffer.
Their is nothing tricky in using this structure.
With this interface on the Recycle Bin (m_pRecycleBin
), it's fairly easy to have a list of the available objects.
This is done by issuing an EnumObjects
call :
LPITEMIDLIST pidl = NULL;
m_pRecycleBin->EnumObjects(m_hWnd, SHCONTF_FOLDERS|SHCONTF_NONFOLDERS|
SHCONTF_INCLUDEHIDDEN, &penumFiles);
if (SUCCEEDED (hr))
{
while (penumFiles->Next(1, &pidl, NULL) != S_FALSE)
{
...
}
}
Cycling in the available objects is interesting, but for myself not sufficient : how do I have access to all the
information from this object? It's always possible to issue a
GetDisplayName
call, but this will give us only the friendly name of the object and
not it's information. After digging for some time, I found an undocumented interface (
IShellDetails
) which has been superseded
on computers having IE5 or higher.
For these machines (all W2K, XP, or above), we are supposed to use the IShellFolder2
interface (instead of the IShellFolder
).
This interface has an interesting method GetDetailsOf
which does what we need.
We can call this function in two different ways :
- When giving a
NULL
value for the first parameter, the function returns the name of the data represented in this column, - By giving instead a
LPCITEMIDLIST
, then the function returns the corresponding information.
The accompanying application use these two ways : The first one to set the names of the columns in the list control; the second one to set all the data.
For the list control :
void CRecycleBinDlg::HeaderFolder2 ()
{
TCHAR szTemp[MAX_PATH];
LPMALLOC pMalloc = NULL;
HRESULT hr = S_OK;
SHELLDETAILS sd;
int iSubItem = 0;
SHGetMalloc(&pMalloc);
while (SUCCEEDED (hr))
{
hr = m_pFolder2->GetDetailsOf (NULL , iSubItem, &sd);
if (SUCCEEDED (hr))
{
switch (sd.str.uType)
{
case STRRET_CSTR:
_tcscpy (szTemp, sd.str.cStr);
break;
case STRRET_OFFSET:
break;
case STRRET_WSTR:
WideCharToMultiByte (CP_ACP, 0, sd.str.pOleStr, -1,
szTemp, sizeof (szTemp), NULL, NULL);
pMalloc->Free (sd.str.pOleStr);
break;
}
m_List.InsertColumn (iSubItem , szTemp, LVCFMT_LEFT, 100);
iSubItem ++;
}
}
pMalloc->Release();
}
Before digging to the values, I was wondering how to obtain the icons of the objects just like do the Explorer.
Again, after searching for some time, I discovered that by issuing a
SHGetFileInfo
on the PIDL
returned by the
EnumObjects
call, I could gain access to the image index from the Shell's ImageList. So I asked
the Shell to share its ImageList with my application. This said, here is what has to be done to enumerate all
the objects from the Recycle Bin :
void CRecycleBinDlg::FillFolder2 ()
{
LPMALLOC pMalloc = NULL;
TCHAR szTemp[MAX_PATH];
LPENUMIDLIST penumFiles;
LPITEMIDLIST pidl = NULL;
SHELLDETAILS sd;
int iItem = 0;
int iSubItem = 0;
int iIndex = -1;
SHFILEINFO fi;
SFGAOF sg = SFGAO_VALIDATE;
HRESULT hr = S_OK;
SHGetMalloc(&pMalloc);
hr = m_pFolder2->EnumObjects(m_hWnd, SHCONTF_FOLDERS |
SHCONTF_NONFOLDERS |
SHCONTF_INCLUDEHIDDEN,
&penumFiles);
if (SUCCEEDED (hr))
{
while (penumFiles->Next(1, &pidl, NULL) != S_FALSE)
{
iItem = m_List.InsertItem (iItem, _T(""));
m_List.SetItemData (iItem, (DWORD)pidl);
ZeroMemory (&fi, sizeof (fi));
hr = SHGetFileInfo ((LPCSTR)pidl, 0, &fi, sizeof (fi),
SHGFI_SYSICONINDEX | SHGFI_SMALLICON |
SHGFI_PIDL);
if (SUCCEEDED (hr))
{
iIndex = fi.iIcon;
m_List.SetItem (iItem, 0, LVIF_IMAGE, NULL, iIndex, 0, 0, 0);
}
hr = S_OK;
iSubItem = 0;
while (SUCCEEDED (hr))
{
hr = m_pFolder2->GetDetailsOf (pidl , iSubItem, &sd);
if (SUCCEEDED (hr))
{
switch (sd.str.uType)
{
case STRRET_CSTR:
_tcscpy (szTemp, sd.str.cStr);
break;
case STRRET_OFFSET:
break;
case STRRET_WSTR:
WideCharToMultiByte (CP_ACP, 0, sd.str.pOleStr, -1,
szTemp, sizeof (szTemp), NULL, NULL);
pMalloc->Free (sd.str.pOleStr);
break;
}
m_List.SetItemText (iItem, iSubItem , szTemp);
iSubItem ++;
}
}
}
}
if (NULL != penumFiles)
{
penumFiles->Release ();
penumFiles = NULL;
}
pMalloc->Release();
}
These points were the beginning of the accompanying application.
After being happy with a list full of items and all the icons, I wondered how, in my program,
I could either delete or undelete a file from the Recycle Bin. I searched for sometime, either by myself and on the Web to find that the most secure way was to call the InvokeCommand
from
the contextual menu of the selected object from the list.
To be successful on this (I spent some hours on it!) at least two things have to be done :
- Don't forget to call
OleInitialize
in the InitInstance
member of your
CWinApp
object, else, the command will never be executed! - Since many of us are using localized systems, we must determine by program the command of the verb to execute.
Before going any further, I must say that I haven't found any documentation on the available verbs (and their CommandID) used in the example;
but on the different systems on which I have used the application (Win98 FR, W2KAS US, .NET AS and W2K workstation FR) they were all the same.
I invite you to have a look on the code to see how I did it.
After having done this, I wanted the list to be synchronized with the real content of the clipboard and discovered two different things :
- First, after simulating a call to the contextual menu, I was refreshing the list...to discover that
I wasn't able to detect any changes. After some researches, I discovered that the call to
InvokeCommand
is an asynchronous call. It means that it returns immediatly, which is why I wasn't able to see any changes. - Second I didn't find any way to be informed that something was done, for example, in the Explorer.
Their again, I had to search on the Web to discover some really useful undocumented APIs. In my case the most
useful were
SHChangeNotifyRegister
and SHChangeNotifyDeregister
. The most complete
information that I found on them were on PCQuest
and Undocumented Windows 95
With this call in mind, what we have to do is to specify for which SHNotify event(s) we want our application to be called,
and to give an Event ID with which the shell is going to call us back. Then in our application, we'll have to create an event handler for the given ID and do what we want inside.
For the fun of discovering what's going on, I have added two Checkboxes which allow to (de)activate the notification registers.
For the Recycle Bin, the code is the following one:
void CRecycleBinDlg::OnChkrbin()
{
UpdateData (TRUE);
if (TRUE == m_ChkRBin)
{
LPPIDLSTRUCT stPIDL;
LPITEMIDLIST ppidl;
pfSHChangeNotifyRegister SHChangeNotifyRegister;
SHChangeNotifyRegister
= (pfSHChangeNotifyRegister)GetProcAddress (m_hShell32,
MAKEINTRESOURCE(2));
if (NULL != SHChangeNotifyRegister)
{
if(SHGetSpecialFolderLocation(GetSafeHwnd(),CSIDL_BITBUCKET,
&ppidl) != NOERROR)
{
AfxMessageBox(_T("GetSpecialFolder problem"));
}
stPIDL.pidlPath = ppidl;
stPIDL.bWatchSubtree = TRUE;
m_hNotifyRBin = SHChangeNotifyRegister (m_hWnd,
SHCNF_ACCEPT_INTERRUPTS | SHCNF_ACCEPT_NON_INTERRUPTS,
SHCNE_ALLEVENTS,
WM_SHELLNOTIFY,
1,
&stPIDL);
if(NULL == m_hNotifyRBin)
{
TRACE(_T("Change Register Failed for RecycleBin"));
}
}
}
else
{
pfSHChangeNotifyDeregister SHChangeNotifyDeregister
= (pfSHChangeNotifyDeregister)GetProcAddress(m_hShell32,
MAKEINTRESOURCE(4));
if (NULL != SHChangeNotifyDeregister)
{
BOOL bDeregister = SHChangeNotifyDeregister(m_hNotifyRBin);
}
}
}
Their isn't anything really special in this. First, we have to get the address of the function to use, and we must do
it with its ordinal since the name of the functions aren't exported.
In the case of registering the notification, the tricky thing is to determine for which event(s) we want
our application to be notified and to give the ID of the event to track. In our case, we're using two different IDs,
since I wanted to be sure what was going on for each kind of event.
When your function is triggered by the Shell, both WPARAM and LPARAM are significant. The first is the address
of a structure which contains PIDLs for the associated BEFORE and AFTER items, and the second is the
ID (SHCNE_xxx) of the event.
The code used in the example is the following one:
LRESULT CRecycleBinDlg::OnShellNotify (WPARAM wParam, LPARAM lParam)
{
LRESULT lReturn = 0;
SHNOTIFYSTRUCT shns;
TCHAR szBefore[MAX_PATH];
TCHAR szAfter[MAX_PATH];
TCHAR szMessage[MAX_PATH];
memcpy((void *)&shns,(void *)wParam,sizeof(SHNOTIFYSTRUCT));
SHGetPathFromIDList((struct _ITEMIDLIST *)shns.dwItem1,
szBefore);
SHGetPathFromIDList((struct _ITEMIDLIST *)shns.dwItem2,
szAfter);
switch (lParam)
{
case SHCNE_RENAMEITEM :
case SHCNE_CREATE :
case SHCNE_DELETE :
case SHCNE_MKDIR :
case SHCNE_RMDIR :
case SHCNE_MEDIAINSERTED :
case SHCNE_MEDIAREMOVED :
case SHCNE_DRIVEREMOVED :
case SHCNE_DRIVEADD :
case SHCNE_NETSHARE :
case SHCNE_NETUNSHARE :
case SHCNE_ATTRIBUTES :
case SHCNE_UPDATEDIR :
case SHCNE_UPDATEITEM :
case SHCNE_SERVERDISCONNECT :
case SHCNE_UPDATEIMAGE :
case SHCNE_DRIVEADDGUI :
case SHCNE_RENAMEFOLDER :
case SHCNE_FREESPACE :
UpdateList ();
break;
default:
wsprintf (szMessage, _T("EventID: %08x %d"),
lParam, lParam);
MessageBox (szMessage);;
break;
}
return lReturn;
}
I'm using the same code for the drive's notifications and I was really surprised by what I discovered:
Since I'm dealing with special folders (system one's for the Recycle Bin), each event is triggered one
time for each of the controlled folders!
The accompanying application is an MFC dialog-based, with nothing special in it, except for what
is the subject of this article. The application hasn't been tested or compiled in UNICODE, and to do it I guess that some
things need to be changed.
I used the application on different systems, but no ones with something before IE5 (like fresh install
of Windows 98 or Windows 95). So I'm not sure of the content of HeaderFolder and FillFolder functions. as
some other people could say, I let this as an exercise for the reader!
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.