11.5 Creating the Namespace Extension

only for RuBoard - do not distribute or recompile

11.5 Creating the Namespace Extension

The extension we will build in this chapter is far more contrived than the previous examples. It does absolutely nothing. The point of this project is to learn how to build a namespace extension. Because most of the code in a namespace extension can be of a proprietary nature, it is best that we use an example that is fairly easy to implement yet covers all of the major features of a namespace extension.

Our project, which we'll call DemoSpace, begins with a junction point under My Computer called Root . Root will contain five "folders" numbered through 4. Each folder will contain "items" numbered through the current folder number. DemoSpace is shown in Figure 11.6.

Figure 11.6. DemoSpace
figs/vshl.1106.gif

When we wrote shell extensions, each extension mapped to one object. That object implemented all the interfaces that were required by the extension. But in this case, things work a little differently. Folders operate independently of the view. It is perfectly legal for a namespace extension to provide several views. So theoretically, folders and views represent a one-to-many relationship.

Namespace extensions will contain several objects, not just one. One object will implement IShellFolder and IPersistFolder . This object can be associated with the action that takes place in the tree view. Another object will implement IShellView , which of course, represents the content or view pane on the right side of Explorer. A third will implement IEnumIDList and is responsible for maintaining and providing the "items" in both the tree view and the content pane.

We will also add a few more classes when the time is right, but for now, let's create a new ActiveX DLL project called DemoSpace. We'll begin by adding three classes. Table 11.7 describes the classes and the interfaces they will implement. Add these to the project.

Table11.7. Required Namespace Classes

Class

Implemented Interfaces

ShellFolder

IShellFolder , IPersistFolder

ShellView

IShellView

EnumIDList

IEnumIDList

We're going to have to write more than just a few lines of code to "wire" up this namespace extension. Previously, we were able to discuss one class at a time, write the code for it, enter in a few registry entries, and we were done. Not so in this case. Things won't make sense if we do it that way. A namespace extension has a certain flow, and we need to follow that flow to make the best sense of it all. Therefore, we will be doing some jumping around between these (and other) classes. Also, as mentioned previously, there is some code in a namespace extension that is generic, and there is more of it that is not. This distinction will be noted whenever applicable .

11.5.1 ShellFolder

As you might expect, we have some vtable swaps for this class. In this case, there are two methods that need to be swapped: CompareIDs and GetUIObjectOf . But this time we have a variation of the swap. Remember, each of these objects operates independently of the others. There might be a case in which two instances of ShellFolder exist at one time. This presents a little problem, which, fortunately, has a simple solution.

If you remember, vtables are shared between every instance of a class. All addresses of the methods that comprise a class are the same for each instance. What does this mean to us? Well, if you haven't noticed, we have been swapping these functions in the Initialize and Terminate events of the class. When a second instance of ShellFolder is instantiated , the functions will be swapped again. Consider this call:

 m_pOldCompareIDs = SwapVtableEntry(ObjPtr(pFolder), _                                     8, _                                     AddressOf CompareIDsX) 

The first time this function is called, the address of the CompareIDs method is swapped out with the CompareIDsX function defined in Demospace.bas . Now, if a second instance of ShellFolder is instantiated before the first instance terminates, this call will be made again. But remember, vtables are global for every instance of a class. So, on the second call, the vtable for the class already contains the address of CompareIDsX . Basically, all that happens in this case is that the same address is copied into the vtable. So our address swapping in the Initialize event is not a problem.

The problem lies in the Terminate event, when we swap the addresses back. If the first instance terminates, swapping the functions back, the second instance is no longer bound to the proper methods. A crash is sure to result.

We will get around this by actually reference counting ShellFolder ourselves . We will maintain a public counter declared in the Demospace.bas code module that is incremented every time Initialize is called and is decremented every time Terminate is called. If the counter is when we terminate, we'll know it's safe to swap the methods back. There are four methods that need to be swapped: BindToObject , CompareIDs , CreateViewObject , and GetUIObjectOf . Let's look at the code for Class_Initialize and Class_Terminate, which is shown in Example 11.6.

Example 11.6. ShellFolder Class_Initialize/Class_Terminate
  'Declared in Demospace.bas  Public g_FolderSwapRef As Long  'ShellFolder.cls  Private m_pOldBindToObject As Long Private m_pOldCompareIDs As Long Private m_pOldCreateViewObj As Long Private m_pOldGetUIObjectOf As Long Private Sub Class_Initialize(  )     Set m_pMalloc = GetMalloc     If g_FolderSwapRef = 0 Then         Dim pFolder As IShellFolder         Set pFolder = Me         m_pOldBindToObject = SwapVtableEntry(ObjPtr(pFolder), _              6, AddressOf BindToObjectX)         m_pOldCompareIDs = SwapVtableEntry(ObjPtr(pFolder), _              8, AddressOf CompareIDsX)         m_pOldCreateViewObj = SwapVtableEntry(ObjPtr(pFolder), _              9, AddressOf CreateViewObjectX)         m_pOldGetUIObjectOf = SwapVtableEntry(ObjPtr(pFolder), _              11, AddressOf GetUIObjectOfX)     End If          g_FolderSwapRef = g_FolderSwapRef + 1      End Sub Private Sub Class_Terminate(  )     g_FolderSwapRef = g_FolderSwapRef - 1          If (g_FolderSwapRef = 0) Then         Dim pFolder As IShellFolder         Set pFolder = Me         m_pOldBindToObject = SwapVtableEntry(ObjPtr(pFolder), _              6, m_pOldBindToObject)          m_pOldCompareIDs = SwapVtableEntry(ObjPtr(pFolder), _              8, m_pOldCompareIDs)         m_pOldCreateViewObj = SwapVtableEntry(ObjPtr(pFolder), _              9, m_pOldCreateViewObj)         m_pOldGetUIObjectOf = SwapVtableEntry(ObjPtr(pFolder), _              11, m_pOldGetUIObjectOf)     End If          Set m_pMalloc = Nothing End Sub 

We will use this same reference counting technique for ShellView and EnumIDList, as well. Each of the Class_Initialize and Class_Terminate events for both of these classes will increment and decrement a counter. Class_Terminate will only swap back the methods in the vtable when the counter is equal to zero.

We will come back to BindToObject , CompareIDs , CreateViewObject and GetUIObjectOf later, since they are significant methods in the grand scheme of things.

For now, take note of the private member variable m_ pMalloc . All of the primary classes in the namespace extension will use IMalloc to allocate memory for PIDLs. In the Class_Initialize event, we call GetMalloc to retrieve a reference to this interface. GetMalloc is shown in Example 11.7.

Example 11.7. GetMalloc
 Public Function GetMalloc(  ) As IMalloc     Dim pMalloc As IMalloc     Dim lpMalloc As Long     Dim hr As Long          hr = SHGetMalloc(lpMalloc)     If (hr = S_OK) Then              CopyMemory pMalloc, lpMalloc, 4                  Set GetMalloc = pMalloc                  End If      End Function 

GetMalloc primarily wraps the function SHGetMalloc , which returns the IMalloc reference to us. SHGetMalloc is found in shell32.dll and is defined like so:

 Public Declare Function SHGetMalloc Lib "shell32.dll" _      (lpMalloc As Long) As Long 

We used CopyMemory before when we had to deal with raw interface addresses. The difference here is that an AddRef is actually being performed when the function returns using Set . So it is safe to set the interface equal to Nothing when we are finished with it.

Now that we have laid the groundwork for ShellFolder, let's continue with implementing the methods the object will need to support. We'll start with GetClassID, only because it stands in the way of more important matters, and then move on from there.

11.5.1.1 GetClassID

The action (albeit there's not much of it) begins with IPersistFolder .

IPersistFolder contains one method, Initialize , that will not be implemented. But the method must return S_OK , or the whole works come tumbling down. In order to return S_OK , we can just leave the method empty. VB handles the rest:

 Private Sub IPersistFolder_Initialize( _      ByVal pidl As VBShellLib.LPCITEMIDLIST)     'Must return S_OK End Sub 

Because IPersistFolder is "derived" from IPersist , it also contains GetClassID . We've seen this method more than a few times now (see Section 5.3.1 in Chapter 5), but we've never actually implemented it. Let's do that now. Example 11.8 contains the implementation.

Example 11.8. IPersistFolder::GetClassID
 Private Sub IPersistFolder_GetClassID(lpClassID As VBShellLib.clsid)          Dim clsid As GUID     Dim sProgID As String     sProgID = "DemoSpace.ShellFolder"          CLSIDFromProgID StrPtr(sProgID), clsid          lpClassID = VarPtr(clsid) End Sub 

This method is quite simple. It is just required to return the CLSID for the object implementing IShellFolder . Not a string representation of the CLSID, mind you, but the actual 128-bit number. This is accomplished by calling CLSIDFromProgID , which is declared as follows :

 Public Declare Function CLSIDFromProgID Lib "ole32.dll" _      (ByVal lpszProgID As Long, pClsid As GUID) As Long 

This method takes a pointer to a program identifier and to a GUID structure, which, as you might recall, is defined like so:

 Public Type GUID     Data1 As Long     Data2 As Integer     Data3 As Integer     Data4(7) As Byte End Type 

With that out of the way, we are ready to create the view object.

11.5.1.2 CreateViewObject

This function is responsible for creating the object that will manage the view. For the most part, this function is generic. Example 11.9 shows the function in its entirety. The most exciting thing about this method is that it is one of the few times we actually have to call IUnknown::QueryInterface ourselves. It has that "I just got my hands dirty" feel to it, doesn't it? This method, like BindToObject , is also passed a reference to an IID. Under Windows 9x and NT, this IID always appears to be IShellView . However, under Windows 2000, the shell sometimes asks for IShellLink . We haven't discussed this latter interface, and we're not going to. But the gist of the IShellLink interface is that it is used for shortcuts. Specifically, this is used to accommodate distributed link tracking, which is a feature of Windows 2000 that enables client applications to track link sources that have moved. CreateViewObject needs to return E_OUTOFMEMORY in the event the shell requests an interface other than IShellView . Therefore, this method is swapped in the vtable with a replacement function.

Example 11.9. CreateViewObject
 'Demospace.bas Public Const IID_IShellView = "{000214E3-0000-0000-C000-000000000046}" Public Function CreateViewObjectX(ByVal this As IShellFolder, _                                    ByVal hwndOwner As hWnd, _                                    ByVal riid As REFIID, _                                    ppvOut As LPVOID) As Long      CreateViewObjectX = E_OUTOFMEMORY      Dim iid As String      iid = GetIID(riid)      If iid = IID_IShellView Then          'Get reference to current shell folder.          Dim pShellFolder As ShellFolder          Set pShellFolder = this          'Create new view.          Dim pShellView As ShellView          Set pShellView = New ShellView          'Pass folder info to view.          pShellView.Initialize pShellFolder, pShellFolder.pidl          'Query view for IShellFolder.          Dim pUnk As IUnknownVB          Set pUnk = pShellView          pUnk.QueryInterface riid, ppvOut          Set pUnk = Nothing          Set pShellView = Nothing          Set pShellFolder = Nothing          CreateViewObjectX = S_OK      End If  End Function 

This quite possibly could be a generic implementation, but look at the call to ShellView.Initialize (not to be confused with Class_Initialize). This call is the equivalent of a C++ constructor. Note, though, that Initialize is not a method defined by the IShellView interface; we've implemented it purely to pass information to the view object class right when it is created. So in this case, we pass an object reference to ShellFolder and a PIDL. PIDLs should contain everything needed to describe the items they represent, so this implementation might suffice (it does for all three example extensions). But there might be times when your own Initialize event will require something a little more exotic. It's up to you. Whatever your view object might need, this is the place to pass it. Anyway, this version passes a PIDL to the view. The view object will use this PIDL to populate its list view control with folders and items.

After we have created an instance of ShellView, we get a reference to IUnknownVB (our no-holds-barred version of IUnknown which is discussed in detail in Chapter 6), and we call QueryInterface with the riid and ppvOut parameters given to us by the shell. With this done, the shell now has a reference to our view object and will call IShellView::CreateViewWindow , which is the method that actually creates the view window and places it in the content pane.

11.5.2 Creating the View

We need to add the Initialize method to ShellView so that the view object can receive the object reference to ShellFolder and the PIDL that represents that folder. We'll do that first, then we will implement CreateViewWindow. Initialize is shown at the bottom of Example 11.10.

Example 11.10. ShellView Initialize
  'ShellView.cls  Private m_pidl As LPITEMIDLIST Private m_parentFolder As ShellFolder Private m_pidlMgr As pidlMgr Private Sub Class_Initialize(  )     Set m_pidlMgr = New pidlMgr End Sub Private Sub Class_Terminate(  )     Set m_pidlMgr = Nothing End Sub  Public Sub Initialize(f As ShellFolder, ByVal pidl As LPITEMIDLIST)     Set m_  parentFolder = f     m_  pidl = m_  pidlMgr.Copy(pidl) End Sub  

Don't worry about the code pertaining to pidlMgr. For now, just know that it is a class we will use to help us work with PIDLs. We'll get to the pidlMgr class when I talk about EnumIDList.

11.5.2.1 CreateViewWindow

The purpose of this function could not be clearer. Its name gives it away. CreateViewWindow is responsible for creating the view window and returning the handle of that window back to the shell. The function is fairly easy to implement, but there is quite a bit going on. The syntax of this method is as follows:

 HRESULT CreateViewWindow(     IShellView *lpPrevView,     LPFOLDERSETTINGS lpfs,     IShellBrowser *psb,     RECT *prcView,     HWND *phWnd ); 

The first parameter, lpPrevView , is a pointer to the view window that was exited before our view object was created. This could be any view window, depending on where we were in the namespace before our extension was activated. This could also be a previous instance of our view object. The Platform SDK also says that this value could be NULL . In any case, we will not use the value. But it could come in handy if you want to communicate with the previous view in your own extension, possibly as an optimization.

The second parameter, lpfs , is very important. It's the address of a FOLDERSETTINGS structure. We don't need to go into details with this structure, but it is important. We will cache this value for later and give it right back to the shell. This parameter is how the shell maintains the state of the viewwhich, in this case, means one of the views (Web Page, Large Icons, Small Icons, List, and Details) defined by Explorerwhen jumping between namespace extensions.

The third parameter, psb , is a reference to IShellBrowser . We will cache this value, as well. Later, we'll use it for a variety of tasks , such as adding menu items and displaying text in Explorer's status bar. The view object will also make use of this parameter to handle browsing into folders from the view pane side of things (versus the tree view side).

The fourth parameter, prcView , is the address of a RECT structure that contains the coordinates of the view pane. We'll create a local instance of this structure using CopyMemory and size our view window to these values.

Last, but not least, we have an HWND , which is an [in, out] parameter. So, when the view window has been created, we will use this parameter to pass the handle back to the shell.

CreateViewWindow is shown in Example 11.11. Take a look, and then we'll discuss the details.

Example 11.11. CreateViewWindow
 'ShellFolder.cls Private m_folderSettings As FOLDERSETTINGS Private Sub IShellView_CreateViewWindow( _      ByVal lpPrevView As VBShellLib.IShellView, _      ByVal lpfs As VBShellLib.LPCFOLDERSETTINGS, _      ByVal psb As VBShellLib.IShellBrowser, _      ByVal prcView As VBShellLib.LPRECT, phWnd As VBShellLib.hWnd)     Dim dwStyle As DWORD     Dim parentWnd As hWnd     Dim rc As RECT              'Save folder settings     CopyMemory m_folderSettings, ByVal lpfs, Len(m_folderSettings)          'Get window rect     CopyMemory rc, ByVal prcView, Len(rc)          Set m_frmView = New frmView  parentWnd = psb.GetWindow         dwStyle = GetWindowLong(m_frmView.hWnd, GWL_STYLE)     dwStyle = dwStyle Or WS_CHILD Or WS_CLIPSIBLINGS     SetWindowLong m_frmView.hWnd, GWL_STYLE, dwStyle     SetParent m_frmView.hWnd, parentWnd  MoveWindow m_frmView.hWnd, rc.Left, rc.Top, _                 rc.Right - rc.Left, rc.bottom - rc.Top, True     ShowWindow m_frmView.hWnd, SW_SHOW          phWnd = m_frmView.hWnd          Set m_pShellBrowser = psb     Set m_frmView.ShellBrowser = m_pShellBrowser          FillList      End Sub 

After the FOLDERSETTINGS have been saved and the view window has been created and sized to the RECT structure, things get a little interesting.

First, we call IShellBrowser::GetWindow ( IShellBrowser is actually derived from IOleWindow ) to get the handle to the content pane window in Explorer. Once we have that, we can use GetWindowLong and SetWindowLong Win32 API functions to actually change the style bits of our window and transform it into a child window. The SetParent API function allows us to set the parent of our newly born child to the window given to us by IShellBrowser . Once this has all been accomplished, we can position our view according to prcView using MoveWindow and then use ShowWindow to display our view.

But we are not quite done. Before we exit the method, we need to give the handle to our view back to the shell. We will also save a private copy of IShellBrowser and give another copy to the view window. Finally, we call FillList to populate our view with items (we'll come back to this function in Section 11.6.9 later in this chapter). FillList is a function we will create to handle populating of the list view.

11.5.2.2 The View window

Of course, for any of the code in Example 11.12 actually to work, we need a view window. This is easy enough. Add a Form to the project called frmView and do the following:

  1. Set its BorderStyle property equal to "None."

  2. Add a list view control called "ListView."

  3. Add a column header to the list view called "Items."

  4. Add an ImageList control.

Now, we need to add a ShellBrowser property to the form, which CreateViewWindow will use to provide the form with a reference to IShellBrowser . The list view also needs to be resized to the form whenever Explorer is resized, so we'll use the MoveWindow API in a resize event to handle the job. Also, the form will eventually need to work with PIDLs, so we'll add a private instance of the mysterious pidlMgr class to the form as well (we'll talk about this class in Section 11.6 later in this chapter). The code for frmView is shown in Example 11.12.

Example 11.12. frmView
 Option Explicit Private m_pidlMgr As pidlMgr Private m_pShellBrowser As IShellBrowser Private Sub Form_Load(  )     Set m_pidlMgr = New pidlMgr End Sub Private Sub Form_Resize(  )     MoveWindow ListView.hWnd, 0, 0, Me.Width, Me.Height, 1 End Sub Private Sub Form_Unload(Cancel As Integer)     Set m_pidlMgr = Nothing     Set m_pShellBrowser = Nothing End Sub Public Property Set ShellBrowser(sb As IShellBrowser)     Set m_pShellBrowser = sb End Property 
11.5.2.3 Back to ShellView

The remaining methods of IShellView , with the exception of UIActivate , can now be implemented. UIActivate , though, will have to remain until later, because it will be different for every namespace that you create.

The last of the IShellView methods are very simple to implement. Each requires a few lines of code. Let's get them out of the way, then we can get to the EnumList class.

11.5.2.4 DestroyWindow

This method is called when Explorer wants to terminate the view window. When this happens we simply unload the form:

 Private Sub IShellView_DestroyViewWindow(  )     Unload m_frmView     Set m_frmView = Nothing End Sub 
11.5.2.5 GetCurrentInfo

This method is called when the shell wants the current folder settings. These folder settings were cached in IShellView::CreateViewWindow (Example 11.12), so all we have to do is pass them back to the shell:

 Private Sub IShellView_GetCurrentInfo( _      ByVal lpfs As VBShellLib.LPFOLDERSETTINGS)     CopyMemory ByVal lpfs, m_folderSettings, Len(m_folderSettings) End Sub 
11.5.2.6 GetWindow

The only responsibility of this method is to return the handle to the view object:

 Private Function IShellView_GetWindow(  ) As VBShellLib.hWnd     IShellView_GetWindow = m_frmView.hWnd End Function 
11.5.2.7 Refresh

This method is called whenever the view is refreshed (i.e., View figs/u2192.gif Refresh is selected from Explorer's menu, or F5 is pressed). This method is fairly generic, but it is possible your needs could be greater. This implementation merely clears the list view and repopulates it:

 Private Sub IShellView_Refresh(  )     SendMessage m_frmView.ListView.hWnd, LVM_DELETEALLITEMS, 0, 0&     FillList End Sub 

The remaining methods (with the exception of UIActivate ) are not implemented.

11.5.3 Enumerating Shell Items

The shell tells the namespace extension to prepare the data that it wants displayed by calling IShellFolder::EnumObjects . The primary responsibility of this method is to create an object that implements IEnumIDList , which it will pass back to the shell. This object, in our case EnumIDList, is responsible for maintaining the list of PIDLs that represent the items the shell will display in either the tree view or the list view. Let's implement IShellFolder::EnumObjects ; then we will move on to the EnumIDList class and see how that works. Enum-Objects is shown in Example 11.13.

Example 11.13. IShellFolder::EnumObjects
 'ShellFolder.class Private m_iLevel As Integer Private Function IShellFolder_EnumObjects( _      ByVal hwndOwner As VBShellLib.hWnd, _      ByVal grfFlags As VBShellLib.DWORD) As VBShellLib.IEnumIDList     Dim e As New EnumIDList     e.CreateEnumList m_iLevel, grfFlags     Set IShellFolder_EnumObjects = e      End Function 

To implement EnumObjects, all we have to do is create an instance of Enum-IDList, which is our class that implements IEnumIDList . Then we pass this object back to Explorer.

Look at the call to EnumIDList::CreateEnumLis t. Before we give EnumIDList over to the shell, we need to actually create the list of items that it will wrap. CreateEnumList is not a method of IEnumIDList , it is a public function we'll add to EnumIDList for the purpose of creating the list of items.

It works like this: CreateEnumList will build a linked list of PIDLs that will be maintained internally by the EnumIDList class. This list of PIDLs contains one or more folders or items for a particular level of the namespace hierarchy. When the shell is ready for these items, it will call IEnumIDList:: Next for a PIDL. Our implementation of IEnumIDList::Next will give the shell a PIDL from this internally maintained linked list. This happens repeatedly until there are no more PIDLs left in the list.

The two parameters to CreateEnumList require some explanation. m_iLevel is the current "level" where we are in the hierarchy. Look back at Figure 11.6 for a moment. The folders and items are in the following format: Type/Level/Index. The m_iLevel parameter represents this level. The second parameter, grfFlags , which is given to us by the shell, is quite important. This will be a value from the following SHCONTF enumeration:

 typedef enum tagSHCONTF{       SHCONTF_FOLDERS = 32,     SHCONTF_NONFOLDERS = 64,     SHCONTF_INCLUDEHIDDEN = 128, } SHCONTF; 

This flag lets us know whether the shell wants "folders" or "items" when it asks us to build the PIDL list. We will use this information to make sure we comply with the shell's request.

11.5.3.1 CreateEnumList

Before we actually implement CreateEnumList , let's get the Class_Initialize and Class_Terminate events out of the way. They are shown in Example 11.14. Once again, ignore the references to the pidlMgr class and the IMalloc reference. We will discuss these two items momentarily.

Example 11.14. EnumIDList Class_Initialize/Terminate
  'EnumIDList.cls  Implements IEnumIDList Private m_pMalloc As IMalloc Private m_pidlMgr As pidlMgr Private m_pOldNext As Long Private Sub Class_Initialize(  )          Set m_pMalloc = GetMalloc     Set m_pidlMgr = New pidlMgr          'Swap     If (g_EnumSwapRef = 0) Then              Dim pEnumIDList As IEnumIDList         Set pEnumIDList = Me              m_pOldNext = SwapVtableEntry(ObjPtr(pEnumIDList), 4, _              AddressOf NextX)     End If          g_EnumSwapRef = g_EnumSwapRef + 1      End Sub Private Sub Class_Terminate(  )  DeleteList  Set m_pidlMgr = Nothing     Set m_pMalloc = Nothing     g_EnumSwapRef = g_EnumSwapRef - 1          If (g_EnumSwapRef = 0) Then         Dim pEnumIDList As IEnumIDList         Set pEnumIDList = Me              m_pOldNext = SwapVtableEntry(ObjPtr(pEnumIDList), 4, _              m_pOldNext)     End If          End Sub 

Notice the call to DeleteList in the Class_Terminate event. We'll talk about this function in Section 11.5.3 later in this chapter, but for now, just know that it is a function that will be called to free the linked list we will create for the PIDLs.

Now on to CreateEnumList. This function will be different for every namespace extension. But its purpose is always the same: to build a list of PIDLs that will be used by IEnumIDList::Next . Let's look at the function, which is shown in Example 11.15; then we'll discuss its nuances .

Example 11.15. CreateEnumList
  'DemoSpace.bas  Public Const g_nMaxLevels = 5  'EnumIDList.class  Public Function CreateEnumList(ByVal iLevel As LPITEMIDLIST, _      ByVal dwFlags As DWORD) As Boolean     Dim i As Integer     Dim pidlNew As LPITEMIDLIST          CreateEnumList = False          If iLevel < g_nMaxLevels Then                  For i = 0 To iLevel             pidlNew = m_pidlMgr.Create(PT_FOLDER, iLevel, i)             If (pidlNew) Then                 AddToEnumList pidlNew             End If             CreateEnumList = True         Next i              End If          'Enumerate the non-folder items (values)     If (dwFlags And SHCONTF_NONFOLDERS) Then                  iLevel = iLevel - 1                  If iLevel <= g_nMaxLevels Then             For i = 0 To iLevel - 1                 pidlNew = m_pidlMgr.Create(PT_ITEM, iLevel, i)                 If (pidlNew) Then                     AddToEnumList pidlNew                 End If             Next i             CreateEnumList = True         End If     End If End Function 

First, the level is checked for validity. The hierarchy is restricted to five levels in this example by the constant g_nMaxLevels . If you look at Figure 11.6, you will see that the hierarchy contains folders with the levels 04.

We will use a For...Next loop to create the folders and items based on this level number that was passed in to the function. But keep this in mind: the implementation of this function is completely arbitrary. If you look at the example code for the sample RegSpace application, this function is implemented in a totally different manner. It uses the registry enumeration API functions to build the list of PIDLs.

The PIDL itself is created with a call to pidlMgr::Create . We will talk about this method in detail in Section 11.6 later in this chapter. For now just look at the call itself. If you examine the parameters to this function, you will see three values: the PIDL type (folder or item), the level of the PIDL item, and the index of the PIDL item. This is the format of our PIDL. If you remember, the PIDL is nothing more than two bytes that specify the size of the PIDL's data, followed by whatever data we want ( terminated by an empty ITEMIDLIST ). Therefore, our PIDL format is the following:

 size/type/level/index 

pidlMgr::Create will create a PIDL in this format for us. We determine whether we are creating folders or non-folders by the dwFlags parameter.

Once we have the PIDL, we need to maintain it in a list of some sort .

11.5.3.2 A linked list in VB?

We will use an internal linked list to maintain our PIDLs. To understand how it works, you need to look at the following structure:

 Public Type PIDLLIST     pNext As Long     pidl As LPITEMIDLIST End Type 

The pidl member is easy to understandit contains the PIDL we want to keep track of. The pNext member contains a pointer to another structure of type PIDLLIST , which is the next PIDL in the list. Using this method, we can chain a list of PIDLs together (see Figure 11.7). This is much more efficient than using ReDim Preserve to build a variable length array, so don't just limit the linked lists to a namespace extension. They are good any time you have a variable-length list of data that needs to be searched efficiently .

Figure 11.7. The linked list
figs/vshl.1107.gif
11.5.3.3 AddToEnumList

Our EnumIDList class will contain three private member variables that correspond to the first member of the list, the current member of the list, and the last member of the list. AddToEnumList uses this information to determine where the next PIDL will go into the list and adjusts these list pointers accordingly . Let's examine the AddToEnumList function, which is shown in Example 11.16.

Example 11.16. AddToEnumList
  'EnumIDList.cls  Public m_pFirst As Long Public m_pCurrent As Long Public m_pLast As Long Public Function AddToEnumList(ByVal pidl As LPITEMIDLIST) As Boolean     Dim aPidlList As PIDLLIST     Dim pNewItem As Long          AddToEnumList = False          'Allocate memory for enum linked list item     pNewItem = m_pMalloc.Alloc(Len(aPidlList))              If (pNewItem > 0) Then                      aPidlList.pNext = 0&         aPidlList.pidl = pidl                  CopyMemory ByVal pNewItem, aPidlList, Len(aPidlList)                  If (m_pFirst = 0) Then             m_pFirst = pNewItem             m_pCurrent = m_pFirst         End If                  If (m_pLast > 0) Then             CopyMemory aPidlList, ByVal m_pLast, Len(aPidlList)             aPidlList.pNext = pNewItem             CopyMemory ByVal m_pLast, aPidlList, Len(aPidlList)         End If                  m_pLast = pNewItem              AddToEnumList = True              End If      End Function 

We'll use the shell's memory allocator for the first time to allocate the memory for the new linked list item.

The PIDL is assigned to the PIDLLIST structure, and pNext is set to 0& . Note that the ampersand in the assignment statement is important. This is a long value that is a NULL address. This marks the end of the list.

The first time AddToEnumList is called, m_ pFirst and m_ pCurrent are both assigned to the PIDLLIST link item. Thereafter, the pNext member of m_ pLast is assigned to the new item, and the new item is added to the end of the list. When the shell starts calling IEnumIDList::Next for PIDLs, we will pass back whatever PIDL is pointed to by m_ pCurrent . m_ pCurrent will then be adjusted to point to the next item in the linked list.

Because we have allocated the memory for the linked list ourselves, when Enum-IDList terminates, we free the list using a call to DeleteList (see Example 11.14). DeleteList is shown in Example 11.17.

Example 11.17. DeleteList
 Private Sub DeleteList(  )     Dim aPidlList As PIDLLIST              Do While (m_pFirst > 0)                  CopyMemory aPidlList, ByVal m_pFirst, Len(aPidlList)         m_pFirst = aPidlList.pNext                  If (aPidlList.pidl > 0) Then             m_pidlMgr.Delete aPidlList.pidl         End If              Loop          m_pFirst = 0     m_pCurrent = 0     m_pLast = 0 End Sub 

Starting with m_ pFirst , DeleteList merely copies the PIDL into a local instance of PIDLLIST , adjusts m_ pFirst to point to the next PIDL in the list, then frees the current PIDL (which is now in aPidlList ) by calling pidlMgr::Delete . This function merely wraps a call to IMalloc::Free .

11.5.3.4 Next

Shortly after we have built our linked list of PIDLs, the shell begins to call several functions repeatedly in an effort to display the PIDL appropriately. It will call IEnumIDList::Next for the PIDL itself. It will call IShellFolder::GetAttributesOf to find out whether this PIDL is a folder or an item. It will call IShellFolder::GetDisplayNameOf for the display text of the PIDL. And it will call IShellFolder::CompareIDs to determine in which order it should display the PIDLs. Then it will call IEnumIDList::Next again. This process repeats until there are no more PIDLs. The process looks like this:

  1. Get PIDL.

  2. Determine attributes: is it a "File" or a "Folder?"

  3. Get the display name of the PIDL.

  4. Compare this PIDL to a previous PIDL to determine the display order.

  5. Start over.

As we mentioned earlier, when the shell calls the Next method, we will give it the next PIDL in our linked list via the rgelt parameter; this is whatever is pointed to by m_ pCurrent . m_ pCurrent is then adjusted to point to the next item in the list. If m_ pCurrent is equal to 0, we know that we are at the end of the list, so we return S_FALSE . The shell will also expect us to tell it how many PIDLs we are returning. Although we will not do this, this method can be written to accommodate returning several PIDLs at once. This process is demonstrated in Example 11.18. Remember, this method has undergone a vtable swap; therefore, it exists in a code module.

Example 11.18. NextX
  'DemoSpace.bas  Public Function NextX(ByVal this As IEnumIDList, _                        ByVal celt As ULONG, _                        rgelt As LPITEMIDLIST, _                        pceltFetched As ULONG) As Long          Dim cEnumIDList As EnumIDList     Set cEnumIDList = this          NextX = S_FALSE     pceltFetched = 0     rgelt = 0          If cEnumIDList.m_pCurrent = 0 Then         Exit Function     End If          Dim aPidlList As PIDLLIST     CopyMemory aPidlList, _                 ByVal cEnumIDList.m_pCurrent, _                 Len(aPidlList)          rgelt = aPidlList.pidl          cEnumIDList.m_pCurrent = aPidlList.pNext     pceltFetched = 1          NextX = S_OK      End Function 
only for RuBoard - do not distribute or recompile


Visual Basic Shell Programming
Visual Basic Shell Programming
ISBN: B00007FY99
EAN: N/A
Year: 2000
Pages: 128

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net