Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / ATL

Tips in Writing Namespace Extensions (II) - Implement Create and Delete Object Operations

4.64/5 (13 votes)
19 Oct 200510 min read 1   2.2K  
This article describes how to implement IContextMenu interface to enable users to create or delete objects in Namespace Extension.

Image 1

Contents

Introduction

Namespace Extension (abbr. NSE) is actually a COM server which implements some required Shell interface. The Explorer uses those COM interfaces to let the user access and manipulate internal namespace data and display it in a graphical form. Before reading this article, a look at the following articles is strongly recommended:

When developing your own NSE, to make your NSE's folder object act like a "real" folder, just providing functions to enable user to navigate between the folder objects in your NSE (the topic is covered in Article 2) is not enough. We should provide other basic functions to create or delete objects, especially the folder objects in our own NSE, like what we do in the System namespace

Solving this problem involves two aspects:

  • First, you should implement the UI related Shell interface, for example IContextMenu, which enables users to send commands.
  • Second, we should handle all the commands and notify all the Shell views and Tree views to reflect the change.

This article assumes that you know C++, ATL, COM and are familiar with the basic knowledge of NSE. The NSE realized in the provided sample project simulates what the user does to create or delete a folder in the System namespace, that is, the user can create or delete object in our NSE by selecting the corresponding menu item provided in the implemented context menu. The sample project is created using ATL COM AppWizard.

Provide context menu in both Tree view and Shell view

Our NSE provides context menu in both Tree view and Shell view to enable users to delete or create objects. The following pictures show the context menus under four different situations:

Image 2

Right click in the selected folder in Tree view.

Image 3

Right click in the selected folder in Shell view.

Image 4

Right click in Shell view when no object is selected.

Image 5

Right click in NSE's root folder.

To provide the context menu in Tree view, you must create a COM object which supports the IContextMenu interface. When the Explorer needs to display a context menu for one of your sub-folders it calls your IShellFolder::GetUIObjectOf, requesting for an IID_IContextMenu interface. The menu items created in our implementation will be merged to folder object's shortcut menu created by the Explorer.

When the user right clicks the mouse in our NSE's Shell view, which sends a WM_CONTEXTMENU message to the Shell view, to handle this message, you can implement a context menu using the standard Win32 menu commands, or you can use the implemented IContextMenu COM object. In our sample project, we have chosen the second way.

Notes: When you right click NSE's root folder, the context menu shown is not created by our NSE, instead, it is created by the Explorer. But we can control a part of this context menu by assigning some attributes (please refer to IShellFolder::GetAttributesOf) to the NSE's root folder when we register our NSE (please refer to "Registering the Extension" section in Article 1). For example, if you want to show the properties of our NSE root folder, you should add the SFGAO_HASPROPSHEET attribute to the "Attributes" value in "ShellFolder" key, to make it really work, you should implement a PropertySheetHandler and register it in the "shellex\PropertySheetHandlers" subkey under the "{NSE's CLSID}" key. In our sample project, we assign SFGAO_HASPROPSHEET to NSE root, but do not implement PropertySheetHandler. Therefore, you can see the "Properties" in the context menu of the root folder, but when you select it, a message "The properties of this item are not available." will be shown.

Provide context menu in Tree view

In our sample project, we add code to our IShellFolder::GetUIObjectOf implementation to support IID_IContextMenu. When IID_IContextMenu is passed in, it creates and instantiates the CContextMenu object and returns the pointer to the IContextMenu interface back to the caller.

STDMETHODIMP CMyVirtualFolder::GetUIObjectOf(HWND hWnd, 
                                    UINT nCount, 
                                    LPCITEMIDLIST* pidls, 
                                    REFIID riid, LPUINT, 
                                    LPVOID* ppRetVal)
{
    HRESULT Hr;
    ...... // handle other interface requests

    if( riid == IID_IContextMenu ) 
    {
        if( nCount!=1 ) 
            return E_FAIL;

        CComObject<CContextMenu>* pContextMenu;
        HR( CComObject<CContextMenu>::CreateInstance(&pContextMenu));

        pContextMenu->AddRef();

        HR( pContextMenu->_Init(this, hWnd, *pidls) );
        Hr=pContextMenu->QueryInterface(IID_IContextMenu, ppRetVal);

        pContextMenu->Release();

        return Hr;
    }    
    ...... //handle other interface requests

    return E_NOINTERFACE;
}

Provide context menu in Shell view

While handling WM_CONTEXTMENU message, if use the common way - call Win32 menu functions to create context menu - we have to maintain an additional set of command handler functions to deal with the menu commands. However, this has been done in CContextMenu's InvokeCommand function; we can make use of this by using the CContextMenu object to create the context menu in Shell view. The advantage is that it will make the maintainability of our source code better.

BEGIN_MSG_MAP(CNSFShellView)
    ...... //declare other msg handler functions
    MESSAGE_HANDLER(WM_CONTEXTMENU, OnContextMenu)
    ...... //declare other msg handler functions  
END_MSG_MAP()
LRESULT CNSFShellView::OnContextMenu(UINT uMsg, 
         WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    LPITEMIDLIST pidlSelected=NULL;
    LPITEMIDLIST *apidls;
    
    apidls = 
        (LPITEMIDLIST*)_Module.m_Allocator.Alloc(
                                1 * sizeof(LPITEMIDLIST));  
    if(apidls == NULL)
        return E_OUTOFMEMORY;

    ::ZeroMemory(apidls,sizeof(LPITEMIDLIST));

    ATLASSERT( NULL != apidls);

    UINT nCount = ListView_GetSelectedCount(m_hwndList);
    if( 0 == nCount) 
    {
        // indicate that no item in list view is selected, 
        // created context menu is bind to current folder
        pidlSelected = NULL;
    }
    else
    {
        LV_ITEM        lvItem;
        int nSelItem = ListView_GetNextItem( m_hwndList, 
                                        -1, LVIS_SELECTED );
        
        ZeroMemory(&lvItem, sizeof(lvItem));
        lvItem.mask = LVIF_PARAM;
        lvItem.iItem = nSelItem;

        if(ListView_GetItem(m_hwndList, &lvItem))
            pidlSelected=(LPITEMIDLIST)(lvItem.lParam);
    }

    apidls[0]=pidlSelected;

    LPCONTEXTMENU  pContextMenu = NULL; 
    m_pFolder->GetUIObjectOf(m_hWnd, 1, 
                            (LPCITEMIDLIST*)apidls, 
                            IID_IContextMenu,NULL,
                            (LPVOID*)&pContextMenu);  

    if(pContextMenu)
    {
        HMENU hMenu = ::CreatePopupMenu();
        if( hMenu && SUCCEEDED(pContextMenu->QueryContextMenu
                  (hMenu,0,MENU_OFFSET, MENU_MAX, CMF_NORMAL)))
        {
            UINT iSelCmdItem=0;

            POINT pt = { LOWORD(lParam), HIWORD(lParam) };
            iSelCmdItem = ::TrackPopupMenu(hMenu,
                      TPM_LEFTALIGN|TPM_LEFTBUTTON|TPM_RETURNCMD,
                      pt.x, pt.y,0,m_hWnd,NULL);
            if( iSelCmdItem > 0)
            {
                CMINVOKECOMMANDINFO  cmi;
                ZeroMemory(&cmi, sizeof(cmi));     
                cmi.cbSize = sizeof(cmi);        
                cmi.hwnd = m_hWnd;           
                cmi.lpVerb = 
                  (LPCSTR)MAKEINTRESOURCE(iSelCmdItem - MENU_OFFSET);       
                pContextMenu->InvokeCommand(&cmi);  
            }
        }
        ::DestroyMenu(hMenu);
        pContextMenu->Release();
    }
    _Module.m_Allocator.Free(apidls);

    return 0;
}

Implement IContextMenu

Now, I'll describe how to implement our ContextMenu object.

To enable the user to delete or create a folder in our NSE, the menu items created in our context menu include: Properties, New NSEFolder, Delete. The behavior of our context menu simulates what happens in a System namespace:

  • Situation 1: When right click focuses the folder object (except the root folder) in Tree view, show "Delete" and "Properties", and Explorer would have already inserted an item "Expand".
  • Situation 2: When right click focuses the object (folder or file) in Shell view, show "Delete" and "Properties".
  • Situation 3: When right clicking in Shell view and no object is selected, show "New NSEFolder" and "Properties".

Notes: For simplicity, "Properties" item is used for demonstration, it is not being implemented.

Self-defined data members and member function

typedef enum
{
    IDM_PROPERTIES =0, //menu identifier offset of Properties
    IDM_CREATE_FOLDER, //menu identifier offset of New folder
    IDM_DELETE,        //menu identifier offset of Delete
    IDM_LAST,
} MENUITEMS;

CMyVirtualFolder *m_pFolder; //refer to the folder object which
                             // provided this ContextMenu object
HWND m_hWnd;                 //handler of the window in which
                             // user right click the mouse
LPITEMIDLIST m_pidl;         //simple PIDL of the object which
                             // you selected
CNWSPidlMgr  m_PidlMgr;
//function use to initialize data members
HRESULT _Init(CMyVirtualFolder *pFolder,
                HWND hWnd, LPCITEMIDLIST pidl);

Initialize the context menu object

HRESULT _Init(CMyVirtualFolder *pFolder,
                  HWND hWnd, LPCITEMIDLIST pidl)
{
    if(pFolder==NULL)
    {
        MessageBox(NULL,
          _T("CContextMenu()::_Init(pFolder==NULL)"),
          _T("NSExtAddDelFld"),MB_OK);
        return E_FAIL;
    }
    m_pFolder = pFolder;
    m_pFolder->AddRef();
    m_pidl = m_PidlMgr.Copy(pidl);
    m_hWnd = hWnd;

    return S_OK;
}

Add commands to the context menu

This standard member function is used to add commands to a context menu:

STDMETHOD(QueryContextMenu)(HMENU hMenu,
                            UINT    iIndexMenu,
                            UINT    idCmdFirst,
                            UINT    idCmdLast,
                            UINT    uFlags)
{
    // add Menu item according to Explore's behavior
    if( NULL == m_pidl )
    {
        //no object is selected, context menu is created
        //in Shell view and is belongs to current folder
        //enable to create new subfolder in current folder
        ::InsertMenu(hMenu, iIndexMenu++,
                  MF_STRING | MF_BYPOSITION,
                  idCmdFirst + IDM_CREATE_FOLDER,
                  _TEXT("New NSE&Folder"));
        ::InsertMenu(hMenu, iIndexMenu++,
                 MF_SEPARATOR | MF_STRING | MF_BYPOSITION,
                 0, _T(""));
        ::InsertMenu(hMenu, iIndexMenu++,
                 MF_STRING | MF_BYPOSITION,
                 idCmdFirst + IDM_PROPERTIES,
                 _TEXT("&Properties"));
    }
    else
    {
        //one object is selected in Shell view or Tree view
        //add delete and properties two commands
        ::InsertMenu(hMenu, iIndexMenu++,
                     MF_STRING | MF_BYPOSITION,
                     idCmdFirst + IDM_DELETE,
                     _TEXT("&Delete"));
        ::InsertMenu(hMenu, iIndexMenu++,
            MF_SEPARATOR | MF_STRING | MF_BYPOSITION,
            0, _T(""));
        ::InsertMenu(hMenu, iIndexMenu++,
                   MF_STRING | MF_BYPOSITION,
                   idCmdFirst + IDM_PROPERTIES,
                   _TEXT("&Properties"));
    }

    ::SetMenuDefaultItem(hMenu, 0, TRUE);

    return MAKE_HRESULT(SEVERITY_SUCCESS, 0, IDM_LAST);
}

Invoke delete and create commands

When a create folder command is invoked, since the process of create folder involves operations in the ListView (user should specify the name of the created folder), we should deliver the create operation to the Shell view object by sending a ID_NEWITEM_FOLDER command to the current Shell view.

If delete command is invoked, we call IShellFolder::_DoDelete to fulfill the task.

STDMETHOD(InvokeCommand)(LPCMINVOKECOMMANDINFO pcmi)
{
    USES_CONVERSION;

    // The command is sent via a verb
    if( HIWORD(pcmi->lpVerb) )
    {
        LPCMINVOKECOMMANDINFOEX pcmix = NULL;
        if(pcmi->cbSize>=
               sizeof(CMINVOKECOMMANDINFOEX)-sizeof(POINT))
            pcmix = (LPCMINVOKECOMMANDINFOEX)pcmi;

        LPCSTR pstr = pcmi->lpVerb;

        // If it's an UNICODE string, convert it to ANSI now
        // NOTE: No C++ block here because of W2CA stack-scope.
        if( (pcmix!=NULL) &&
            ((pcmix->fMask & CMIC_MASK_UNICODE)!=0) &&
            (pcmix->lpVerbW!=NULL) )
            pstr = W2CA(pcmix->lpVerbW);

        if( strcmp(pstr, "Delete")==0 )
            pcmi->lpVerb = (LPCSTR)IDM_DELETE;
        else if( strcmp(pstr, "New NSEFolder")==0 )
            pcmi->lpVerb = (LPCSTR)IDM_CREATE_FOLDER;
        else
            return E_INVALIDARG;
    }

    // Check that it's a valid command
    if( LOWORD(pcmi->lpVerb)>IDM_LAST )
        return E_INVALIDARG;

    // Process our command
    switch( LOWORD(pcmi->lpVerb) )
    {
        case IDM_DELETE:
        {
            //let IShellFolder accomplish this task
            m_pFolder->_DoDelete(m_pidl);
        }
        break;
        case IDM_CREATE_FOLDER:
        {
            //this command is only support in Shell view
            //so send ID_NEWITEM_FOLDER to Shell view object
            if(m_pFolder->_IsViewWindow(m_hWnd))
                ::SendMessage(m_hWnd,WM_COMMAND,
                                     ID_NEWITEM_FOLDER,0);
            else
                MessageBox(NULL,
                 _T("Don't support create new folder " +
                              "except in Shell View window"),
                 _T("Error"),MB_OK);
        }
        break;
        default:
            return E_INVALIDARG;
    }
    return S_OK;
}

Refresh tree view and shell view

When users delete or create new folders in our NSE, Tree view and Shell view should correctly respond to these changes.

Notify tree view

The shell function SHChangNotify enables us to inform shell about the changes that has happened in our NSE. Especially, when we create or delete a folder, the tree view must reflect these changes correctly.

To make SHChangeNotify really work, we must have the full PIDLs of each item affected by the changes. All the PIDLs mentioned so far were relative PIDLs, but fortunately they use the Concatenate method of our PIDL manage class, so we can calculate each object's full PIDL in our NSE from _Module.m_pidlNSFRoot which saves our NSE's root folder's full PIDL and the complex PIDL of the object.

Please refer to the "Folder initialization" and "Concatenate PIDLs" sections in Article 2 to know more about the _Module.m_pidlNSFRoot and the Concatenate methods.

Refresh shell view

As we know, the IShellview interface provides a method Refresh which is used to refresh the shell view. But this method can only be called from inside the IShellview.

We can make use of this method. While implementing IShellview, we can define a user command ID_VIEW_REFRESH and register a command handler to it, and in the command handler we can simply call IShellview::Refresh. Therefore, once we have the handle of the shell view window, we can refresh this window by sending the ID_VIEW_REFRESH command to it.

When more than one Explorer windows coexist, while deleting or creating a folder, we have to refresh all the shell views in them. When we find out all the handles of shell view windows and send ID_VIEW_REFRESH command to each window then the problem is solved.

Sometimes, we need to refresh all the other shell views except the current one.

BOOL CALLBACK RefreshShellView( HWND hWnd, LPARAM lParam )
{
    if( hWnd ) 
    {
        TCHAR szClassName[MAX_PATH]=_T("");
        DWORD dwLen=MAX_PATH;
        GetClassName(hWnd,szClassName,dwLen);
        if( (_tcscmp(szClassName,_T("ExploreWClass"))==0) ||
            (_tcscmp(szClassName,_T("CabinetWClass"))==0) )
        {
            HWND hwndShellView = 
                FindWindowEx(hWnd,NULL,_T("NSFViewClass"),NULL);
            if(hwndShellView !=NULL)
            {           
                HWND hwndExcept =(HWND)lParam;
                if((hwndExcept!=NULL && hwndExcept!=hwndShellView) ||
                    (hwndExcept==NULL))
                    ::SendMessage(hwndShellView,WM_COMMAND,
                                               ID_VIEW_REFRESH,0);
            }
        }
    }
    return( TRUE );
}

void RefreshShellViewWndsExcept(HWND hwndExcept)
{
    // continue looping until done
    for(; !EnumWindows((WNDENUMPROC) RefreshShellView,
                              (LPARAM) hwndExcept ); ); 
}

Delete object

The implementation of deleting an object in NSE has three parts:

  • Delete the corresponding item from the shell view - to realize this, we first update our configuration data and then refresh the shell view according to the updated configuration data.
  • Notify shell that an item has been deleted - Call SHChangeNotify with SHCNE_RMDIR if delete folder or SHCNE_DELETE if delete file to inform shell and let all the tree views in Explorer Windows reflect the delete operation.
  • Manage your NSE's data - that is, delete the corresponding item from the configuration file.

Actually, _DoDelete can be a member function of any implemented COM object in our NSE as long as the object can access the IShellFolder interface of the current folder object, for example, CContextMenu object. I usually choose IShellFolder.

_DoDelete

HRESULT CMyVirtualFolder::_DoDelete(LPITEMIDLIST pidl)
{
    HRESULT Hr;
    int ret;

    ret=MessageBox(NULL,
        _TEXT("This Delete operation will direct " + 
                  "to the deleted item can't be recover," + 
              "\nAre you sure to delete the selected item?"), 
              _TEXT("Notice"),MB_OKCANCEL|MB_ICONWARNING);
    if(ret==IDOK)
    {
        //1.Delete corresponding item from configuration file
        HR(DeleteItemInCfgFile(m_pidlPath,pidl));
        
        //2. Refresh the Tree view
        LPITEMIDLIST tmpPidl1,tmpPidl2;
        tmpPidl1=m_PidlMgr.Concatenate(
                    _Module.m_pidlNSFROOT,m_pidlPath);
        tmpPidl2=m_PidlMgr.Concatenate(tmpPidl1,pidl);

        if( NWS_FOLDER == (m_PidlMgr.GetItemType(pidl)) )
            ::SHChangeNotify( SHCNE_RMDIR, 
                SHCNF_IDLIST | SHCNF_FLUSH, tmpPidl2, NULL);
        else
            ::SHChangeNotify( SHCNE_DELETE, 
                SHCNF_IDLIST | SHCNF_FLUSH, tmpPidl2, NULL);

        // Ask Shell to refresh current directory
        ::SHChangeNotify(SHCNE_UPDATEDIR, 
                SHCNF_IDLIST | SHCNF_FLUSH, tmpPidl1, NULL);

        m_PidlMgr.Delete(tmpPidl1);
        m_PidlMgr.Delete(tmpPidl2);
        
        //3. Refresh Shell view Exist in All Explorer Windows.
        RefreshShellViewWndsExcept(NULL);
    }
    return S_OK;
}

Create new folder object

IShellView

To enable your list view to add new items, LVS_EDITLABELS style must be assigned to the list view window.

The create folder process in our NSE can be divided into the following steps:

  1. First, add a folder item with "New Folder" with a temporary name in the configuration file.
  2. Refresh the shell view to show the created folder with the temporary name.
  3. Refresh the tree view to show the created folder with the temporary name.
  4. Set the newly created item in the ListView into Edit mode, so that the user can assign a name to it.
  5. When editing is complete, call IShellFolder::SetNameOf to update the corresponding item's name in the configuration file and create a new PIDL with the new name, then call SHChangeNotify to inform the Shell to let all tree views reflect the rename operation.
  6. Locate the newly created item in ListView, update the item's PIDL (which binds to the item's lParam member) with the newly created PIDL and delete the old one.
  7. Refresh all the other shell views except the current one.

All the above steps can be realized with the help of three IShellView's handler functions (OnNewFolder, OnLabelEditBegin, OnLabelEditEnd) and two IShellFolder's standard methods (GetAttributesOf and SetNameOf).

Related message and command handlers

BEGIN_MSG_MAP(CNSFShellView)
    ......
    COMMAND_ID_HANDLER(ID_NEWITEM_FOLDER, OnNewFolder)
    NOTIFY_CODE_HANDLER(LVN_BEGINLABELEDIT, OnLabelEditBegin)
    NOTIFY_CODE_HANDLER(LVN_ENDLABELEDIT, OnLabelEditEnd) 
    ......
END_MSG_MAP()

ID_NEWITEM_FOLDER command handler

This command handler begins the create operation. It takes charge of the first four steps described above:

LRESULT CNSFShellView::OnNewFolder(WORD /*wNotifyCode*/, 
          WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
    TCHAR szTempNewName[MAX_PATH]=_T("");
    
    //1.Add dir item to current folder's 
    //section in configuration file 
    CreateFolderInCfgFile(m_pFolder->m_pidlPath,szTempNewName);

    //2.Refresh current Shell view to show the new created folder
    Refresh();

    //3.Refresh Other Shell view to show new folder 
    RefreshShellViewWndsExcept(m_hWnd);

    //4.Refresh Tree view
    //4.1 Get new folder's PIDL structure
    LVFINDINFO fi = { 0 };
    fi.flags = LVFI_STRING;
    fi.psz = szTempNewName;
    int iItem = ListView_FindItem(m_hwndList, -1, &fi);
  
    LVITEM lvItem = { 0 };
    lvItem.mask = LVIF_PARAM;
    lvItem.iItem = iItem;
    ListView_GetItem(m_hwndList, &lvItem);
 
    //4.2 Send Notify To Shell
    LPITEMIDLIST tmpPidl1,tmpPidl2;
    tmpPidl1=m_PidlMgr.Concatenate(_Module.m_pidlNSFROOT,
                                    m_pFolder->m_pidlPath);
    tmpPidl2=m_PidlMgr.Concatenate(tmpPidl1,
                             (LPCITEMIDLIST)lvItem.lParam);
    ::SHChangeNotify(SHCNE_MKDIR, 
               SHCNF_IDLIST | SHCNF_FLUSH, tmpPidl2, NULL);
    m_PidlMgr.Delete(tmpPidl1);
    m_PidlMgr.Delete(tmpPidl2);
 
    //5.Put new created item in edit mode...
    ::SetFocus(m_hwndList);
    ListView_EditLabel(m_hwndList, iItem);

    return 0;
}

LVN_BEGINLABELEDIT notify handler

This event handler is mainly responsible for calling IShellFolder::GetAttributesOf to verify whether the current label is editable or not:

LRESULT CNSFShellView::OnLabelEditBegin(UINT /*CtlID*/, 
                          LPNMHDR lpnmh, BOOL& /*bHandled*/)
{
    NMLVDISPINFO* lpdi = (NMLVDISPINFO*) lpnmh;
    DWORD dwAttr = SFGAO_CANRENAME;
    m_pFolder->GetAttributesOf(1, 
             (LPCITEMIDLIST*)&lpdi->item.lParam, &dwAttr);
    if( (dwAttr & SFGAO_CANRENAME)==0 ) 
        return TRUE;
    return FALSE; //Return FALSE means allow to edit
}

LVN_ENDLABELEDIT notify handler

Happens when the user completes the editing. The user specified new name will be passed in, after checking up its validity. We then continue with the remaining three steps in creating a new folder.

LRESULT CNSFShellView::OnLabelEditEnd(UINT /*CtlID*/, 
                         LPNMHDR lpnmh, BOOL& /*bHandled*/)
{
    NMLVDISPINFO* lpdi = (NMLVDISPINFO*) lpnmh;
    
    //1.If user cancelled editing
    if((lpdi->item.pszText==NULL) || 
          (_tcscmp(lpdi->item.pszText,_T("New Folder")) == 0))
         return FALSE; 

    //2.validate the new name
    //2.1 Not include illegal character
    if( _tcspbrk(lpdi->item.pszText, _T("<>/?:\"\\")) != NULL )        
    {
         MessageBox(NULL,_T("File name can't include " + 
                        "following characters: \\/:*?\"<>|"), 
                    _T("Error"),MB_OK);
         ::SetFocus(m_hwndList);
         ListView_EditLabel(m_hwndList,lpdi->item.iItem);
         return FALSE;
    }
    
    //2.2 uniquness
    TCHAR szCfgFile[MAX_PATH]=_TEXT("");
    TCHAR szSection[MAX_PATH]=_TEXT("");
    OpenDirInCfgFile(m_pFolder->m_pidlPath,szSection,szCfgFile);

    DWORD dwLen=MAX_PATH;
    TCHAR szDirKeyValue[MAX_PATH]=_T("");
    GetPrivateProfileString(szSection,_T("dir"),_T(""),
                               szDirKeyValue, dwLen, szCfgFile);

    if(NotUniqueName(lpdi->item.pszText,szDirKeyValue)==TRUE)
    {
         MessageBox(NULL,_T("Rename error: Specified filename is " + 
            "equal to a file existed! Please specify another name."), 
            _T("Error"),MB_OK);
         ::SetFocus(m_hwndList);
         ListView_EditLabel(m_hwndList,lpdi->item.iItem);
         return FALSE;
    }

    //3.Get Old PIDL
    LVITEM lvItem = { 0 };
    lvItem.mask = LVIF_PARAM;
    lvItem.iItem = lpdi->item.iItem;
    ListView_GetItem(m_hwndList, &lvItem);
    LPITEMIDLIST pidlOldItem = (LPITEMIDLIST)lvItem.lParam;

    //4. Call IShellFolder::SetNameOf Create New PIDL for new Name...
    USES_CONVERSION;
    LPCWSTR pwstr = T2CW(lpdi->item.pszText);
    LPITEMIDLIST pidlNewItem = NULL;
    HRESULT Hr = m_pFolder->SetNameOf(NULL, pidlOldItem, 
                                     pwstr, 0, &pidlNewItem);
    if( FAILED(Hr) || (pidlNewItem==NULL) ) 
    {
        ::MessageBox(NULL,
          _T("IShellFolder::SetNameOf failed."),_T("Error"),MB_OK);
        return FALSE;
    }

    //5.Set the new PIDL to item
    lvItem.mask = LVIF_PARAM;
    lvItem.lParam = (LPARAM)pidlNewItem;
    ListView_SetItem(m_hwndList, &lvItem);

    m_PidlMgr.Delete(pidlOldItem);
    
    //6.Refresh Other Shell View to show new name
    RefreshShellViewWndsExcept(m_hWnd);

    return TRUE; // Accept rename
}

IShellFolder related functions

Whether this folder be renamed?

Because the rename operation involves the create folder process, we should assign the SFGAO_CANRENAME attribute to all the folder objects to implement IShellFolder::GetAttributesOf, like this:

STDMETHODIMP CMyVirtualFolder::GetAttributesOf(UINT uCount, 
                                      LPCITEMIDLIST pidls[], 
                                      LPDWORD pdwAttribs)
{
    ......
    case NWS_FOLDER:
    {
        ......
        //enable rename to folder objects
        dwAttr |=SFGAO_CANRENAME; 
        ......
    }
    ......
}

Sets object's display name and change its PIDL

The implementation of this function is responsible for:

  • Creating a PIDL with the new name.
  • Updating the newly created item in the configuration file with the user specified name.
  • Notifying all the tree views to reflect the rename.
STDMETHODIMP CMyVirtualFolder::SetNameOf(HWND, 
                                LPCITEMIDLIST pidlOld, 
                                LPCOLESTR pstrName, 
                                DWORD, 
                                LPITEMIDLIST* ppidlOut)
{
    USES_CONVERSION;
    if( ppidlOut!=NULL ) 
        *ppidlOut=NULL;
 
    DWORD dwAttr = SFGAO_CANRENAME;
    GetAttributesOf(1, &pidlOld, &dwAttr);
    if( (dwAttr & SFGAO_CANRENAME)==0 ) 
        return E_FAIL;
 
    TCHAR szNewName[MAX_PATH+1];
    if( wcslen(pstrName)>MAX_PATH ) 
        return E_FAIL;
    _tcscpy( szNewName, OLE2CT(pstrName));

    //1.Create new PIDL for the new display name
    LPITEMIDLIST  pidlNew = NULL;
    ITEM_TYPE iItemType = NWS_FOLDER;
    pidlNew=m_PidlMgr.Create(iItemType,szNewName);
    if(!pidlNew)
        return E_FAIL;

    //2.Replace name to szNewName in Configuration File
    HRESULT Hr;
    HR(ReplaceNameInCfgFile(m_pidlPath,szNewName));

    //3.Notify Shell so it can rename the item in the Explorer tree
    LPITEMIDLIST pidlFullPath;
    pidlFullPath=m_PidlMgr.Concatenate(
                        _Module.m_pidlNSFROOT,m_pidlPath);

    LPITEMIDLIST pidlFullOld,pidlFullNew;
    pidlFullOld=m_PidlMgr.Concatenate(pidlFullPath,pidlOld);
    pidlFullNew=m_PidlMgr.Concatenate(pidlFullPath,pidlNew);

    ::SHChangeNotify(SHCNE_RENAMEFOLDER, 
        SHCNF_IDLIST |SHCNF_FLUSH, pidlFullOld, pidlFullNew);
    ::SHChangeNotify(SHCNE_UPDATEDIR, 
        SHCNF_IDLIST | SHCNF_FLUSH, pidlFullPath, NULL);
    
    m_PidlMgr.Delete(pidlFullPath);
    m_PidlMgr.Delete(pidlFullOld);
    m_PidlMgr.Delete(pidlFullNew);

    *ppidlOut = pidlNew;
    return S_OK;
}

Conclusion

This article describes how to implement delete or create object function in your own NSE. I plan to discuss the most complicated topic in NSE - drag and drop in my next article.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here