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






4.64/5 (13 votes)
Oct 19, 2005
10 min read

176320

2158
This article describes how to implement IContextMenu interface to enable users to create or delete objects in Namespace Extension.
Contents
- Introduction
- Provide context menu in both Shell view and Tree view
- Refresh Tree view and Shell view
- Delete object
- Create new folder object
- Conclusion
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:
- Article 1: The Complete Idiot's Guide to Writing Namespace Extensions - Part I by Michael Dunn - it introduces the basic knowledge and skills required for developing NSE, and the terms used in it will continue to be used in this article.
- Article 2: Tips in Writing Namespace Extension (I) - Implements Subfolder by myself - the sample code of the current article is based on the code in Article 2.
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:
Right click in the selected folder in Tree view.
Right click in the selected folder in Shell view.
Right click in Shell view when no object is selected.
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
withSHCNE_RMDIR
if delete folder orSHCNE_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:
- First, add a folder item with "New Folder" with a temporary name in the configuration file.
- Refresh the shell view to show the created folder with the temporary name.
- Refresh the tree view to show the created folder with the temporary name.
- Set the newly created item in the ListView into Edit mode, so that the user can assign a name to it.
- 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 callSHChangeNotify
to inform the Shell to let all tree views reflect the rename operation. - 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.
- 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.