Contents
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.
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.
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;
......
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;
}
......
return E_NOINTERFACE;
}
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)
......
MESSAGE_HANDLER(WM_CONTEXTMENU, OnContextMenu)
......
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)
{
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;
}
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.
typedef enum
{
IDM_PROPERTIES =0,
IDM_CREATE_FOLDER,
IDM_DELETE,
IDM_LAST,
} MENUITEMS;
CMyVirtualFolder *m_pFolder;
HWND m_hWnd;
LPITEMIDLIST m_pidl;
CNWSPidlMgr m_PidlMgr;
HRESULT _Init(CMyVirtualFolder *pFolder,
HWND hWnd, LPCITEMIDLIST pidl);
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;
}
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)
{
if( NULL == m_pidl )
{
::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
{
::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);
}
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;
if( HIWORD(pcmi->lpVerb) )
{
LPCMINVOKECOMMANDINFOEX pcmix = NULL;
if(pcmi->cbSize>=
sizeof(CMINVOKECOMMANDINFOEX)-sizeof(POINT))
pcmix = (LPCMINVOKECOMMANDINFOEX)pcmi;
LPCSTR pstr = pcmi->lpVerb;
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;
}
if( LOWORD(pcmi->lpVerb)>IDM_LAST )
return E_INVALIDARG;
switch( LOWORD(pcmi->lpVerb) )
{
case IDM_DELETE:
{
m_pFolder->_DoDelete(m_pidl);
}
break;
case IDM_CREATE_FOLDER:
{
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;
}
When users delete or create new folders in our NSE, Tree view and Shell view should correctly respond to these changes.
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.
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)
{
for(; !EnumWindows((WNDENUMPROC) RefreshShellView,
(LPARAM) hwndExcept ); );
}
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
.
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)
{
HR(DeleteItemInCfgFile(m_pidlPath,pidl));
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);
::SHChangeNotify(SHCNE_UPDATEDIR,
SHCNF_IDLIST | SHCNF_FLUSH, tmpPidl1, NULL);
m_PidlMgr.Delete(tmpPidl1);
m_PidlMgr.Delete(tmpPidl2);
RefreshShellViewWndsExcept(NULL);
}
return S_OK;
}
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 call SHChangeNotify
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
).
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()
This command handler begins the create operation. It takes charge of the first four steps described above:
LRESULT CNSFShellView::OnNewFolder(WORD ,
WORD , HWND , BOOL& )
{
TCHAR szTempNewName[MAX_PATH]=_T("");
CreateFolderInCfgFile(m_pFolder->m_pidlPath,szTempNewName);
Refresh();
RefreshShellViewWndsExcept(m_hWnd);
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);
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);
::SetFocus(m_hwndList);
ListView_EditLabel(m_hwndList, iItem);
return 0;
}
This event handler is mainly responsible for calling IShellFolder::GetAttributesOf
to verify whether the current label is editable or not:
LRESULT CNSFShellView::OnLabelEditBegin(UINT ,
LPNMHDR lpnmh, BOOL& )
{
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;
}
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 ,
LPNMHDR lpnmh, BOOL& )
{
NMLVDISPINFO* lpdi = (NMLVDISPINFO*) lpnmh;
if((lpdi->item.pszText==NULL) ||
(_tcscmp(lpdi->item.pszText,_T("New Folder")) == 0))
return FALSE;
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;
}
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;
}
LVITEM lvItem = { 0 };
lvItem.mask = LVIF_PARAM;
lvItem.iItem = lpdi->item.iItem;
ListView_GetItem(m_hwndList, &lvItem);
LPITEMIDLIST pidlOldItem = (LPITEMIDLIST)lvItem.lParam;
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;
}
lvItem.mask = LVIF_PARAM;
lvItem.lParam = (LPARAM)pidlNewItem;
ListView_SetItem(m_hwndList, &lvItem);
m_PidlMgr.Delete(pidlOldItem);
RefreshShellViewWndsExcept(m_hWnd);
return TRUE;
}
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:
{
......
dwAttr |=SFGAO_CANRENAME;
......
}
......
}
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));
LPITEMIDLIST pidlNew = NULL;
ITEM_TYPE iItemType = NWS_FOLDER;
pidlNew=m_PidlMgr.Create(iItemType,szNewName);
if(!pidlNew)
return E_FAIL;
HRESULT Hr;
HR(ReplaceNameInCfgFile(m_pidlPath,szNewName));
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;
}
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.