Click here to Skip to main content
15,867,453 members
Articles / Desktop Programming / MFC
Article

How to programmatically use the Recycle Bin

Rate me:
Please Sign up or sign in to vote.
4.90/5 (29 votes)
19 Aug 2002CPOL7 min read 290.4K   4.3K   79   54
An article on how to access the content of the RecycleBin, track its changes and deal with its functionnalities (delete-restore)

Sample Image - RecycleBin.jpg

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;
// Iterate through list
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); // windows memory management pointer 
                               // needed later

    // We'are asking the object the list of available columns.
    // For each, we are adding them to the control in the right order.
    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); // windows memory management pointer needed later

    // Get the list of available objects
    hr = m_pFolder2->EnumObjects(m_hWnd, SHCONTF_FOLDERS | 
                                         SHCONTF_NONFOLDERS | 
                                         SHCONTF_INCLUDEHIDDEN, 
                                 &penumFiles);
    if (SUCCEEDED (hr))
    {
        // Iterate through list
        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);
            }

            // We iterate now in all the available columns.
            // Since it depends on the system, we "hope" that they are 
            // going to be as many  and in the same order as when we 
            // have added the column's headers.

            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 :

  1. Don't forget to call OleInitialize in the InitInstance member of your CWinApp object, else, the command will never be executed!
  2. 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 :

  1. 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.
  2. 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,   // Message that would be sent by the Shell
                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          : //0x00000001L
    case SHCNE_CREATE              : //0x00000002L
    case SHCNE_DELETE              : //0x00000004L
    case SHCNE_MKDIR               : //0x00000008L
    case SHCNE_RMDIR               : //0x00000010L
    case SHCNE_MEDIAINSERTED       : //0x00000020L
    case SHCNE_MEDIAREMOVED        : //0x00000040L
    case SHCNE_DRIVEREMOVED        : //0x00000080L
    case SHCNE_DRIVEADD            : //0x00000100L
    case SHCNE_NETSHARE            : //0x00000200L
    case SHCNE_NETUNSHARE          : //0x00000400L
    case SHCNE_ATTRIBUTES          : //0x00000800L
    case SHCNE_UPDATEDIR           : //0x00001000L
    case SHCNE_UPDATEITEM          : //0x00002000L
    case SHCNE_SERVERDISCONNECT    : //0x00004000L
    case SHCNE_UPDATEIMAGE         : //0x00008000L
    case SHCNE_DRIVEADDGUI         : //0x00010000L
    case SHCNE_RENAMEFOLDER        : //0x00020000L
    case SHCNE_FREESPACE           : //0x00040000L
        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!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
France France
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralRe: Interesting Pin
Eric Lapouge20-Aug-02 9:18
Eric Lapouge20-Aug-02 9:18 
GeneralRe: Interesting Pin
-Kirill-26-Aug-02 21:49
-Kirill-26-Aug-02 21:49 
GeneralRe: Interesting Pin
nie1733-Nov-02 22:59
nie1733-Nov-02 22:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.