|
||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Contents
IntroductionThis article shows you an almost complete implementation of a Shell Namespace Extension. It is the result of nights passed to achieve two goals:
The second goal was the spec I had to implement. This sample will in fact act as a Favorites 'shortcut' system for the DirectoryOpus file manager (well, far more than a file manager, see DOpus). Before going on with this article, you should read Michael Dunn's 'The Complete Idiot's Guide to Writing Namespace Extensions - Part I ', which is the base I used to realize this one. Some more infos are available in Microsoft Knowledge Base Article - 216954, named "HOWTO: Support Common Dialog Browsing in a Shell Namespace Extension". RequirementsSystemBecause this extension makes use of theSHCreateShellFolderView function, you must have IE 4.0 minimum
installed on your system. Note that for WinNT4 (and also Win95 if applicable)
Active Desktop (ships with IE 4) has to be installed even if not enabled.
I tested it on several Windows platforms, here is the list with some comments:
Since this extension works well on the previously listed platforms, it should also for these (but I didn't test them):
Development EnvironementThe environement I used to create this sample is MSDev 6.0 with ATL 3.0. I also successfully compiled it (though not the MinSize builds) under MSDev 7.0 with ATL 7.0.You must have the Microsoft Platform SDK, I used the one from February 2003. This is important because I use recently documented API. So if you have an old SDK (for example shipped with MSDev) it will not compile. What will this extension do?This extension will act as a Favorites shortcut system. Unlike the system Favorites folder, I do not want to use a .lnk file for each shortcut (which is a text file representing the shortcut). The shortcuts I want are stored in the Registry. As told in the intro of this article, the shortcuts are the one stored by DirectoryOpus. What composes a shortcut?From now on, I will use the term Favorite instead of shortcut, so of what a Favorite is made?
You certainly noticed that the name can contain any chars, including the one that are not allowed in paths. Wanted behaviourThe root view of my namespace has to show the Favorites, their names, and if in 'Details' mode, the path and the rank.The behaviour when the user selects one of the Favorite is to go to the real path of the item. This is the only behaviour needed, what the user makes after this does not concern us, but the target path namespace. This sounds like it should be simple and stupid to implement. Aaaah, but like always, there are ten ways to do the same thing with computers, and different programs (read different MS teams) will use all of them and maybe one or two more. How is it implemented?Okay, let's go to the real work. What should we implement to achieve the wanted behaviour? Valuable informations lie in the Platform SDK, check it for details.PIDL layoutFirst of all, we must choose a PIDL layout. What are our items? They are
Favorites. What is a Favorite composed of? A name, a path and a rank. I choosed
to embed all these infos in the PIDL. For this I have a class Example to get the Path from a PIDL: LPOLESTR pOleStr = CDataFavo::GetPath( pidl ); See that string type: The LPITEMIDLIST pidl;
CDataFavo Favo;
Favo.SetName(_T("_/# Fancy Temp Dir #\_"));
Favo.SetPath(_T("E:\\Temp"));
Favo.SetRank(3);
pidl = m_PidlMgr.Create(Favo);
Use cases for ExplorerI'll describe the control flow of the extension with use cases. I'll not go into deep explanations for all these use cases, please read the code, trace it, modify it. If you stil miss something, ask it on the message board.You will note that there are differences between the Explorer behaviour and the FileDialog behaviour. I assume you have an explorer with the TreeView (at the left) enabled. I'll call the right view (where you see the items) the ShellView. The first thing Explorer or any other controler will do is call our
Then the user will do one of the followings: Clicking on the Namespace icon in the TreeViewSo what happens when you click on the namespace icon in the TreeView? Another method to have exactly the same behaviour is when you double-click the namespace icon in the ShellView (when the Desktop content is shown). Explorer will want to show the namespace items, for this it calls
Clicking the plus sign of the Namespace icon in the TreeViewExplorer will call this sequence:
The result is the tree that gets expanded with our items (if they are
Clicking a favorite item in the TreeViewNow if the user clicks on one of our item in the tree,
We have the target path (in our pidl) and we must HRESULT hr;
CComPtr<IShellFolder> DesktopPtr;
hr = SHGetDesktopFolder(&DesktopPtr);
if (FAILED(hr))
return hr;
LPITEMIDLIST pidlLocal;
hr = DesktopPtr->ParseDisplayName(NULL, pbcReserved,
CDataFavo::GetPath(pidl), NULL, &pidlLocal, NULL);
if (FAILED(hr))
return hr;
hr = DesktopPtr->BindToObject(pidlLocal, pbcReserved, riid, ppvOut);
ILFree(pidlLocal);
return hr;
Double-clicking a favorite item in the ShellViewAlthough the behaviour should be the same as clicking a favorite item in the TreeView, explorer does it in another fashion. In fact, double-clicking an item in the ShellView corresponds to invoking the default entry of the item's context menu. When exploring folders, the default entry is "Explore", this is why the behaviour is the same. Note that you can implement a context menu with a different default entry and thus changing the 'double-click' behaviour. This means that explorer will call our
To see how this can be done, read the next paragraph. Right-clicking a favorite item in the ShellViewHere a context menu has to be displayed. This menu is related to the selected item. If several items are selected, the menu has to display entries that applies to all of the selected items. For my extension, I choosed to display the context menu of the target path, so I don't have to implement one myself. I also only handle single selected items, this is because each item can point to a different storage. So, I can't easily delegate (that means without tons of code) the context menu to different storages at the same time. Explorer will call our To achieve this, we must first convert the target path to an absolute pidl, this is done like that: hr = SHGetDesktopFolder(&DesktopPtr);
if (FAILED(hr))
return hr;
LPITEMIDLIST pidlLocal;
hr = DesktopPtr->ParseDisplayName(NULL, NULL,
CDataFavo::GetPath(*pPidl), NULL, &pidlLocal, NULL);
if (FAILED(hr))
return hr;
Now LPITEMIDLIST pidlRelative;
// pidlTmp will point to the single-item pidl of the target path
LPITEMIDLIST pidlTmp = ILFindLastID(pidlLocal);
// Now strips the last part of the pidl, to have the pidl of the parent
pidlRelative = ILClone(pidlTmp);
ILRemoveLastID(pidlLocal);
// We can now get the parent IShellFolder
hr = DesktopPtr->BindToObject(pidlLocal, NULL,
IID_IShellFolder, (void**)&TargetParentShellFolderPtr);
ILFree(pidlLocal);
if (FAILED(hr))
{
ILFree(pidlRelative);
return hr;
}
hr = TargetParentShellFolderPtr->GetUIObjectOf(hwndOwner, 1, (LPCITEMIDLIST*)&pidlRelative, riid, puReserved, ppvReturn);The redirection is made, if any of the previous function fails the context menu is simply not shown. This can happen when the target path doesn't exist or is inaccessible (network). Use cases for the FileDialogThe FileDialog which is the one from Common Dialogs, behaves a little different than explorer.First the namespace icon has to be displayed in the upper ComboBox, for that
we must register our extension with the following flags:
I don't remember exactly at which condition (OS, shell version, use case) but
not implementing IShellFolder2 can lead to problems, so implement it. The only
added method is Choosing the Namespace icon in the upper ComboBoxThe FileDialog will simply call
Double-clicking a favorite item in the ViewFor a reason I don't really understand, the FileDialog will not simply call
It first calls So how are we concerned about this? The FileDialog will use a
The called sequence for this use case is (removing calls to
GetData(). The purpose is to get the
item pidl. All other methods can simply return E_NOTIMPL.
Let's take a look at it: STDMETHODIMP CDataObject::GetData(LPFORMATETC pFE, LPSTGMEDIUM pStgMedium)
{
// Is the caller requesting a pidl?
if (pFE->cfFormat == m_cfShellIDList)
{
// Return the item pidl in the form of a CIDA structure
pStgMedium->hGlobal = CreateShellIDList(m_pidlParent,
(LPCITEMIDLIST*)&m_pidl, 1);
if (pStgMedium->hGlobal)
{
pStgMedium->tymed = TYMED_HGLOBAL;
// Even if our tymed is HGLOBAL, WinXP calls ReleaseStgMedium()
// which tries to call pUnkForRelease->Release() : BANG!
// (if not NULL)
pStgMedium->pUnkForRelease = NULL;
return S_OK;
}
}
return E_INVALIDARG;
}
When Beware of the line Right-clicking a favorite item in the ShellViewLike in Explorer, this will show a context menu. The behaviour is the same as
for explorer (see Right-clicking a favorite item in
the ShellView) but before calling Problems with Office FileDialogYes, the Office FileDialog is not the one from CommonDialog, it is a modified one.Note that MSDev 7.x (.net) also has this modified FileDialog. It has two drawbacks: FileSystem existance checkThe first drawback is that it will check every folder that you are browsing, and complain if it is not a valid File System folder (a valid path). It gets the folder path by calling our To resolve this, we must return a valid path instead of "::{GUID}". Because my extension doesn't have an install folder, I decided to return a path that should be present in all systems: the temporary directory. The first part of the STDMETHODIMP CShellFolder::GetDisplayNameOf(
LPCITEMIDLIST pidl, DWORD uFlags, LPSTRRET lpName)
{
if ((pidl == NULL) || (lpName == NULL))
return E_POINTER;
// Return name of Root
if (pidl->mkid.cb == 0)
{
switch (uFlags)
{
case SHGDN_NORMAL | SHGDN_FORPARSING :
// <- if wantsFORPARSING is present in the regitry
TCHAR TempPath[MAX_PATH];
if (GetTempPath(MAX_PATH, TempPath) == 0)
return E_FAIL;
return SetReturnString(TempPath, *lpName) ? S_OK : E_FAIL;
}
// We dont' handle other combinations of flags
return E_FAIL;
}
// Getting item names follows here
...
...
}
There is one more thing to do. Like I said before, the FileDialog will call
Sub-items browsingAnother modified behaviour is when the Office FileDialog browses sub-items.
It still calls the root Until now our I modified the code of // Handle multi-level pidl differently if (!m_PidlMgr.IsSingle(pidl)) { HRESULT hr; hr = SHGetDesktopFolder(&DesktopPtr); if (FAILED(hr)) return hr; LPITEMIDLIST pidlLocal; hr = DesktopPtr->ParseDisplayName(NULL, pbcReserved, CDataFavo::GetPath(pidl), NULL, &pidlLocal, NULL); if (FAILED(hr)) return hr; // Bind to the root folder of the favorite folder CComPtr<IShellFolder> RootFolderPtr; hr = DesktopPtr->BindToObject(pidlLocal, NULL, IID_IShellFolder, (void**)&RootFolderPtr); ILFree(pidlLocal); if (FAILED(hr)) return hr; // And now bind to the sub-item of it return RootFolderPtr->BindToObject(m_PidlMgr.GetNextItem(pidl), pbcReserved, riid, ppvOut); } // Here comes the previous code ... First I check if it's a single level pidl or not. Then I get the target path
pidl, like in the previous code. Then I get the IShellFolder of the target path
with the first Shell ViewThe system ShellView provided by the newly documented API, but existing from shell 4.7x,SHCreateShellFolderView() does much
of the work. To populate and handle the items, it will call our
IShellFolder methods and also IShellFolder2 methods.
Basically the only thing we have to do is implement these methods which, for the
majority of them, is already done.
Be aware that not implementing WARNING: I didn't use When using Here is an example, handling one message: #include <ShellFolderView.h> // This one contains the base class class CShellView : public CShellFolderViewImpl { public: // The message map BEGIN_MSG_MAP(CShellView) MESSAGE_HANDLER(SFVM_COLUMNCLICK, OnColumnClick) END_MSG_MAP() // When a user clicks on a column header in details mode LRESULT OnColumnClick(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) { // Shell version 4.7x doesn't understand S_FALSE // as described in the SDK. SendFolderViewMessage(SFVM_REARRANGE, wParam); return S_OK; } The Sometimes your handler has to send messages to the view, this is done with
the method To provide a standard view, you do not have to handle all the messages described in the SDK. In my extension I handle only two messages and it works great. Explorer will request a ShellView by calling your
STDMETHODIMP CShellFolder::CreateViewObject(HWND hwndOwner,
REFIID riid, void** ppvOut)
{
// Make sure the caller requested an IShellView
if (riid == IID_IShellView)
{
// Create the view object
CComObject<CShellView>* pViewObject;
hr = CComObject<CShellView>::CreateInstance(&pViewObject);
if (FAILED(hr))
return hr;
// AddRef the object while we are using it
pViewObject->AddRef();
// Create the view
hr = pViewObject->Create((IShellView**)ppvOut, hwndOwner,
(IShellFolder*)this);
// We are finished with our own use of the view object
// (AddRef()'ed by Create() if successfull)
pViewObject->Release();
return hr;
}
// We do not handle other objects
return E_NOINTERFACE;
}
That's it! Check the code of my extension, it contains some error checking and also a mechanism to trace all the ShellView messages. This can be usefull to investigate a little more. Points of InterestATL buildsWhen creating an ATL project, the I didn't use much of them, mostly string and memory (str* and mem*)
functions. Also static objects. This brings me to using AtlAux which does
minimal CRT-like things and redirects string functions to Windows API. You can
find it here on CodeProject. For the memory functions, I simply got them from
the CRT sources and put them in So here are the different configurations:
How to add the extension in the Places Bar of the File Dialog?Take a look at the picture at the top of this article. See that icon in the left pane of the FileDialog? This is a shortcut icon that will act like selecting our namespace icon in the upper ComboBox. But how to do that?Everything lie in the registry, take a look at
ConclusionThis is the first version of this namespace extension, it implements basic features but it shows how to achieve them, this was the goal of this article. At the time of writing I'm already developing the second version which will cover folders and sub-folders. I'll update this article once I've finished with it.I wrote this article after finishing and not during the development, so I may have loosed focus on some issues. Let me know if you miss some information or if parts of this article are too obscure. History
| |||||||||||||||||||||||||||||||||||