11.6 The PIDL Manager

only for RuBoard - do not distribute or recompile

11.6 The PIDL Manager

Before we continue, we really need to discuss pidlMgr.cls . This class is a helper class that we will use to manage functions involving PIDLs. These helper functions include things like creating, copying, and deleting PIDLs, getting the last PIDL in a list of PIDLs, getting the next PIDL in a list of PIDLs, as well as additional functions that are more specific to our particular namespace extension.

So add a new class to the project called pidlMgr.cls , and let's start implementing some of the functionality of this class.

11.6.1 Delete

We have used three functions from this class so far: Delete , Copy , and Create . Delete is by far the easiest of the functions to implement. It just wraps a call to IMalloc::Free . Delete looks like this:

  'pidlMgr.cls  Private m_  pMalloc As IMalloc Private Sub Class_Initialize(  )     Set m_  pMalloc = GetMalloc End Sub Private Sub Class_Terminate(  )     Set m_  pMalloc = Nothing End Sub  Public Sub Delete(ByVal pidl As LPITEMIDLIST)     m_  pMalloc.Free pidl End Sub  

11.6.2 Copy

Copy is used to make a copy of the PIDL:

 Public Function Copy(ByVal pidlSource As LPITEMIDLIST) As LPITEMIDLIST     Dim pidlTarget As LPITEMIDLIST     Dim cbSource As UINT          Copy = 0          If (pidlSource = 0) Then         Exit Function     End If          cbSource = GetSize(pidlSource)          pidlTarget = m_pMalloc.Alloc(cbSource)     If (pidlTarget > 0) Then         CopyMemory ByVal pidlTarget, ByVal pidlSource, cbSource         Copy = pidlTarget     End If      End Function 

11.6.3 Create

This method is a little more involved, but the reason for this requires some background information. We have already discussed the format of the PIDL with which we will be dealing in this example. It looks like this:

 size/type/level/index 

Therefore, you might expect to create a UDT to represent this PIDL. Perhaps something like the following:

 Type PIDL     iSize As Integer     pType As Long     iLevel As Integer     iIndex As Integer End Type 

Unfortunately, we cannot do this. The data that comprises the PIDL must be sequential. That is, the PIDL data must immediately follow the two bytes indicating the size of the PIDL itself. The UDT that has been described will not work, because VB aligns UDTs on 4-byte boundaries (Long values). What does this mean exactly? Well, look at the UDT for a moment. The first member, iSize , is an Integer. In VB, for backward-compatibility reasons, that is a 2-byte value. When we talk about aligning data on 4-byte boundaries, this means VB will place two empty bytes immediately after iSize to pad the member to 4-bytes. The same is done with iLevel and iIndex . So, rather than having sequential data, we have data with holes in it, as Figure 11.8 illustrates.

Figure 11.8. PIDL aligned on 4-byte boundaries
figs/vshl.1108.gif

The simple solution to this problem is to use CopyMemory and to build our PIDL in memory as if it were a 0-byte aligned structure. Let's step through Create a few lines at a time, starting with the beginning of the method in Example 11.19, and discuss the function.

Example 11.19. Create from pidlMgr
 Public Enum PIDLTYPE    PT_FOLDER = 0    PT_ITEM = 1 End Enum Public Function Create(ByVal pt As PIDLTYPE, ByVal iLevel As Integer, ByVal iIndex As Integer) As LPITEMIDLIST     Dim iSize As Integer     Dim pidl As LPITEMIDLIST          'pidl size bytes (2) + pt(4) + iLevel(2) + iIndex(2)     iSize = 10     'Allocate memory for PIDL + a NULL ITEMIDLIST entry.     pidl = m_pMalloc.Alloc(iSize + 2) 

The first thing that happens is that the memory for the PIDL is allocated. The size of our PIDL is 10 bytes. That's 2 bytes for the size, 4 bytes for the type, 2 bytes for the "level," and 2 bytes for the "index." Therefore, we use IMalloc to allocate 10 bytes for our PIDL :

 If (pidl > 0) Then                  CopyMemory ByVal pidl, iSize, 2 

If the memory was allocated successfully (it is always prudent to check for this), then the size of the PIDL is copied into the first 2 bytes (the size) of the memory that will hold the PIDL. We can then use pointer arithmetic to copy the remaining values into the proper locations. Since we know our PIDL size is 2 bytes, we can skip 2 bytes forward in memory (PIDL + 2) and get the address for the PIDLTYPE . PIDLTYPE is 4 bytes, so jumping forward 6 bytes (PIDL + 6) will give us the address of the PIDLs level, and so on:

 'Copy data into pidl         CopyMemory ByVal pidl + 2, pt, 4     'PIDLTYPE         CopyMemory ByVal pidl + 6, iLevel, 2 'PIDL level          CopyMemory ByVal pidl + 8, iIndex, 2 'PIDL index 

Finally, we need to terminate the PIDL with a NULL , ITEMIDLIST . We could just terminate the PIDL with 2 bytes containing 0s, specifying a PIDL of no size. But to be technically accurate, we will end the PIDL with 2 NULL bytes (refer back to the SHITEMID structure). We then return the PIDL back to the caller:

 'Add empty ITEMIDLIST to end of PIDL         CopyMemory ByVal pidl + iSize, 0, 2     End If          Create = pidl End Function 

Because we have created the PIDL in this manner, we will write several helper functions in PidlMgr to help us extract these values: GetPidlSize , GetPidlType , GetPidlLevel , and GetPidlIndex . Of the four functions, only one, GetPidlSize , can ever be used again. After all, the PIDL's size will always be the first 2 bytes of the structure. Even so, when you write your own namespace extensions, you will have to write similar functions to retrieve information from the PIDL. The rest are specific to the extension. These functions are shown in Example 11.20. They are self-explanatory.

Example 11.20. pidlMgr Helper Functions
  'pidlMgr.cls  Public Function GetPidlSize(ByVal pidl As LPITEMIDLIST) As Integer     Dim iSize As Integer     GetPidlSize = 0     If (pidl = 0) Then         Exit Function     End If     'Size is the first 2 bytes of the pidl     CopyMemory iSize, ByVal pidl, 2     GetPidlSize = iSize End Function Public Function GetPidlType(ByVal pidl As LPITEMIDLIST) As PIDLTYPE     Dim pt As Integer     GetPidlType = 0     If (pidl = 0) Then         Exit Function     End If     'The "level" of the pidl is stored in bytes 3-6     CopyMemory pt, ByVal pidl + 2, 4     GetPidlType = pt End Function Public Function GetPidlLevel(ByVal pidl As LPITEMIDLIST) As Integer     Dim iLevel As Integer     GetPidlLevel = 0     If (pidl = 0) Then         Exit Function     End If     'The "level" of the pidl is stored in bytes 7-8.     CopyMemory iLevel, ByVal pidl + 6, 2     GetPidlLevel = iLevel End Function Public Function GetPidlIndex(ByVal pidl As LPITEMIDLIST) As Integer     Dim iIndex As Integer     GetPidlIndex = 0     If (pidl = 0) Then         Exit Function     End If     'The "index" of the pidl is stored in bytes 9-10.     CopyMemory iIndex, ByVal pidl + 8, 2     GetPidlIndex = iIndex End Function 

We will add new functions to PidlMgr as needed. But for now let's see what happens after our list has been built and the shell has called IEnumIDList::Next .

11.6.4 GetAttributesOf

Now that the shell has one of our PIDLs (as a result of calling IEnumIDList::Next ) it needs to determine whether that PIDL is a folder or not. It does this by asking us, by way of a call to IShellFolder::GetAttributesOf . This method is defined as follows :

 HRESULT GetAttributesOf(UINT cidl, LPCITEMIDLIST *apidl, ULONG *rgfInOut); 

The first parameter, cidl , is the length of the PIDL array that is being pointed to by the second parameter. This is important. We are not getting a PIDL here. We are getting a pointer to an array of PIDLs. This method should be coded to handle more than one PIDL, even though it will not come into play in the example. It merely provides us the opportunity to write some really dangerous code involving pointer arithmetic. The second parameter, rgfInOut , is an [in, out] parameter that will be assigned one or more values from the SFGAO enumeration. We are only concerned with two of these values, SFGAO_FOLDER and SFGAO_HASSUBFOLDER . Let's look at the implementation, which begins at Example 11.21.

Example 11.21. IShellFolder::GetAttributesOf
 Private Sub IShellFolder_GetAttributesOf( _      ByVal cidl As VBShellLib.UINT, _      aPidl As VBShellLib.LPCITEMIDLIST, _      rgfInOut As VBShellLib.ULONG)     Dim i As UINT     Dim dwAttribs As DWORD     Dim pidl As LPITEMIDLIST              dwAttribs = dwAttribs Or 0     rgfInOut = -1     For i = 0 To cidl - 1  CopyMemory pidl, aPidl + (i * 4), 4  

Everything here is fairly straightforward until we get to the call to CopyMemory . This just increments the address of aPidl by 4 (the size of a pointer to an ITEMIDLIST which is a PIDL) every time we iterate the loop. This will happen cidl times:

 If m_pidlMgr.GetPidlType(pidl) = PT_FOLDER Then             dwAttribs = dwAttribs Or SFGAO_FOLDER 

Now that we have the PIDL, we can call PidlMgr::GetPidlType to retrieve the type of the PIDL. If it is a folder, then we add SFGAO_FOLDER to our attributes DWORD :

 If m_pidlMgr.GetPidlLevel(pidl) < g_nMaxLevels Then                 dwAttribs = dwAttribs Or SFGAO_HASSUBFOLDER             End If                      End If              Next i          rgfInOut = rgfInOut And dwAttribs      End Sub 

Next, we'll get the PIDL level. If it's less than g_nMaxLevels , we know that is has subfolders beneath it. Therefore, we turn on the SFGAO_HASSUBFOLDER bits in the attributes DWORD . This will cause Explorer to draw the "+" node next to the folder.

When all is said and done, we return the PIDL attributes by way of rgfInOut .

Of course, this is all very specific to the example for the chapter. When you write your own namespace extensions, the format that you decide to create for your PIDL needs to take functions like GetAttributesOf into consideration. A good rule of thumb is that a PIDL should be able to describe its name and location. Go through the additional sample code that is provided with this chapter and see how this method is implemented in those examples. This should give you a better feel for writing extensions of your own.

11.6.5 GetDisplayNameOf

The shell also needs a way to determine the text to display for a PIDL. It does this by calling IShellFolder::GetDisplayNameOf . Its syntax is as follows:

 HRESULT GetDisplayNameOf(LPCITEMIDLIST pidl, DWORD uFlags,     LPSTRRET lpName); 

The first parameter, pidl , is the PIDL for which the shell wants display information.

The second parameter, uFlags , is a value from the SHGDN enumeration. This value tells us how the shell is trying to display the name. Table 11.8 describes the values from this enumeration. Our chapter example does not use these values, but the additional examples do. If you want more details, check them out. Here, we're just going to return the same string no matter what the circumstances.

Table11.8. SHGDN Enumeration

Constant

Description

SHGDN_NORMAL

The name is a full name relative to the desktop and not to any specific folder.

SHGDN_INFOLDER

The name is relative to the folder that is processing the name.

SHGDN_NORMAL

The name will be used for generic display.

SHGDN_FORADDRESSBAR

The name will be used for display in the address bar combo box.

SHGDN_FORPARSING

The name will be used for parsing. It can be passed to ParseDisplayName .

The last parameter, lpName , is the address of an STRRET structure, which is defined as follows:

 Public Const STRRET_WSTR = &H0 Public Type STRRET     uType As UINT     pOLESTR As Long End Type 

This parameter is how we'll return the display name to the shell.

Let's examine the implementation for GetDisplayNameOf , which is shown in Example 11.22.

Example 11.22. IShellFolder::GetDisplayNameOf
 Private Sub IShellFolder_GetDisplayNameOf( _      ByVal pidl As VBShellLib.LPCITEMIDLIST, _      ByVal uFlags As VBShellLib.DWORD, _      ByVal lpName As VBShellLib.LPSTRRET)     Dim pString As Long     Dim szText As String     Dim szID As String     Dim dwFlags As DWORD         dwFlags = uFlags And &HFF00  szText = m_ pidlMgr.GetPidlName(pidl) & vbNullChar  pString = m_pMalloc.Alloc(LenB(szText))     If (pString) Then         CopyMemory ByVal pString, ByVal StrPtr(szText), LenB(szText)     End If          Dim sret As STRRET     sret.uType = STRRET_WSTR     sret.pOLESTR = pString          CopyMemory ByVal lpName, sret, Len(sret) End Sub 

The first interesting thing that happens is that we call PidlMgr::GetPidlName to build the display name of the PIDL. This function merely uses the helper functions we talked about earlier ( GetPidlType , GetPidlLevel , GetPidlLevel , and GetPidlIndex ) to build a string in the following format:

 Folder/Item Level-Index. 

Next, memory for the string is allocated using IMalloc , and the contents of the string are copied into that location. This makes it "global" to the shell's address space. If we didn't do this, szText would go out of scope as soon as the method terminated .

Finally, we populate the STRRET structure. The first member is set to STRRET_WSTR to inform the shell that the pointer we are giving it is to a wide character string. Then the second member is assigned a string pointer.

There is something very important worth mentioning at this point. The lpName parameter is being used to return information back to the shell, yet the parameter is defined as [in] in the type library. This is done with a purpose in mind. If we had defined this as an [in, out] parameter, we could have coded the last part of this method like so:

 Dim sret As STRRET sret.uType = STRRET_WSTR sret.pOLESTR = pString lpName = VarPtr(sret) 

This sure looks better than the CopyMemory call, doesn't it? The problem is that the shell now has a pointer to a structure that is about to go out of scope. So the information contained in this structure is literally destroyed moments later. We could kludge this by declaring the structure as a private member to the shell folder class; then it would stay in scope for as long as the class was alive . But if we do this for every structure we need to pass back to the shell, things start to get confusing (as if they aren't confusing enough with us hacking the vtables of all these classes).

By defining this parameter as [in] , VB declares it as ByVal , which forces us to actually copy the contents of the structure to the address specified by lpName . Therefore, every time we have a pointer to a structure, we will always define it as [in] .

11.6.6 CompareIDs

IShellFolder::CompareIDs is called by the shell to give the namespace an opportunity to sort the PIDLs that are about to be displayed. If you look at a directory in Explorer, the Details view contains four columns : Name, Size, Type, and Modified. Explorer sorts the list based on the column you select. This is CompareIDs at work.

When CompareIDs is called, the shell passes us two PIDLs, and our implementation needs to determine which one should be displayed first:

  • If PIDL #1 is displayed first, we return 1.

  • If PIDL #2 is displayed first, we return a number > 0.

  • If the PIDLs are the same, we return 0.

Our implementation is simple. First, we make sure folders are displayed first, then we sort on the level, and finally we sort on the index (see Figure 11.6). Before we jump into the code, here is an important concept to remember. The PIDLs we will be getting here will not always contain one item. The PIDL really does represent a path back to the root of the extension. We will get PIDLs that look like this:

 Folder 1-1/Folder 2-2/Folder 3-1/Folder 4-1/Item 1-1 

This list contains five ITEMIDLIST s. The PIDL is the pointer to this list. This is an important concept (which is why we're going over it again and again). A PIDL is not one thing. It is a pointer to a list such as this.

What the shell will give us here is a pointer to the first item in the array of ITEMIDLIST sFolder 1-1. The value we want for sorting purposes, however, is Item 1-1. Therefore, CompareIDs will use a method in the PidlMgr class called GetLastItem to get this value. This method is defined in Example 11.23.

Example 11.23. The GetLastItem Method
 Public Function GetLastItem(ByVal pidl As LPITEMIDLIST) _      As LPITEMIDLIST     Dim pidlLast As LPITEMIDLIST     Dim pidlTemp As LPITEMIDLIST         pidlTemp = pidl     Dim iSize As Integer          iSize = GetPidlSize(pidlTemp)          Do While (iSize > 0)         pidlLast = pidlTemp         pidlTemp = GetNextItem(pidlTemp)         iSize = GetPidlSize(pidlTemp)     Loop              GetLastItem = pidlLast      End Function 

PidlMgr::GetLastItem makes use of a helper function, GetNextItem , which retrieves the next shell identifier in a PIDL. This function is defined in Example 11.24.

Example 11.24. The GetNextItem Method
 Public Function GetNextItem(ByVal pidl As LPITEMIDLIST) _      As LPITEMIDLIST     GetNextItem = 0          If (pidl > 0) Then                  Dim iSize As Integer         iSize = GetPidlSize(pidl)                  'Pointer arithmetic in BASIC (scary, huh?)         GetNextItem = pidl + iSize              End If      End Function 

And finally, here we get to CompareIDs. It has been swapped in the vtable because we need to return specialized values. Example 11.25 contains the implementation.

Example 11.25. CompareIDsX
  'DemoSpace.bas  Public Function CompareIDsX(ByVal this As IShellFolder, _                              ByVal lParam As lParam, _                              ByVal pidl1 As LPCITEMIDLIST, _                              ByVal pidl2 As LPCITEMIDLIST) As Long          Dim pidlMgr As pidlMgr     Set pidlMgr = New pidlMgr          Dim pidlTemp1 As LPITEMIDLIST     Dim pidlTemp2 As LPITEMIDLIST          Dim pt1 As PIDLTYPE     Dim pt2 As PIDLTYPE     Dim iLvl1 As Integer     Dim iLvl2 As Integer     Dim iIndex1 As Integer     Dim iIndex2 As Integer          'Default - pidls are equal     CompareIDsX = 0          pidlTemp1 = pidlMgr.GetLastItem(pidl1)     pidlTemp2 = pidlMgr.GetLastItem(pidl2)          pt1 = pidlMgr.GetPidlType(pidlTemp1)     pt2 = pidlMgr.GetPidlType(pidlTemp2)     If (pt1 <> pt2) Then         CompareIDsX = pt1 - pt2         Exit Function     End If          iLvl1 = pidlMgr.GetPidlLevel(pidlTemp1)     iLvl2 = pidlMgr.GetPidlLevel(pidlTemp2)     If (iLvl1 <> iLvl2) Then         CompareIDsX = iLvl1 - iLvl2         Exit Function     End If          iIndex1 = pidlMgr.GetPidlIndex(pidlTemp1)     iIndex2 = pidlMgr.GetPidlIndex(pidlTemp2)     CompareIDsX = iIndex1 - iIndex2      End Function 

This is the first function to check if you write a namespace extension and items are not getting displayed in the proper order or the behavior of the extension seems odd.

This is one of the most crucial functions you must write when implementing a namespace. If this function does not work, nothing will.


11.6.7 GetUIObjectOf

When the shell requires additional interfaces to carry out tasks for the namespace extension, it calls IShellFolder::GetUIObjectOf . For instance, when the shell needs to display icons for the namespace extension, it calls GetUIObjectOf and asks for an IExtractIcon interface. If the shell wants to display a context menu, it will ask for an IContextMenu interface. In this way, GetUIObjectOf is sort of a specialized QueryInterface. Its syntax is:

 HRESULT GetUIObjectOf(HWND hwndOwner, UINT cidl,                        LPCITEMIDLIST *apidl, REFIID riid,                        UINT *prgfInOut, LPVOID *ppvOut); 

The first parameter, hwndOwner , is the parent window to use if the extension needs to display a message box. This is of no consequence to the VB programmer, because the MsgBox function supplied by Visual Basic does not allow one to specify the parent.

The second parameter, cidl , is the count of the PIDLs in the third parameter, apidl . This method can be coded similarly to IShellFolder::GetAttributesOf to handle more than one PIDL if that functionality is needed. It is usually not, however.

The third parameter, apidl , is a pointer to a GUID that represents the interface the shell is interested in acquiring from us. We will write a function that will take this pointer and convert it into a string that represents this interface. This will allow us to use simple comparisons to figure out which interface the shell is asking for.

The fourth parameter, prgfInOut , is reserved and is not used.

The fifth parameter, ppvOut , is the address that will receive the interface pointer.

To implement this method, we will rely heavily on a function that we will write called GetIID . This function takes a pointer to a GUID and returns a string representation of the GUID. This function is shown Example 11.26.

Example 11.26. GetIID Function
 Public Function GetIID(ByVal riid As REFIID) As String     Dim aGuid As GUID     CopyMemory aGuid, ByVal riid, Len(aGuid)          Dim pClsid As Long     StringFromCLSID aGuid, pClsid          Dim strOut As String * 255     StrFromPtrW pClsid, strOut          Dim iid As String     GetIID = Left(strOut, InStr(strOut, vbNullChar) - 1)     End Function 

GetIID makes use of a function called StringFromCLSID , which is found in ole32.dll . The function is declared as follows:

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

The function doesn't return an actual string that can be used by VB, but rather a pointer to a string. Therefore, we need a function that will take this pointer and return us a string. This function is called StringFromPointer , and it is shown in Example 11.27.

Example 11.27. StringFromPointer Function
 Public Sub StringFromPointer(pOLESTR As Long, strOut As String)          Dim b(255) As Byte     Dim iTemp As Integer     Dim iCount As Integer     Dim i As Integer          iTemp = 1          'Walk the string and retrieve the first byte of each WORD.     While iTemp <> 0         CopyMemory iTemp, ByVal pOLESTR + i, 2         b(iCount) = iTemp         iCount = iCount + 1         i = i + 2     Wend          'Copy the byte array to our string.     CopyMemory ByVal strOut, b(0), iCount      End Sub 

Working with a CLSID

There are four helper functions found in ole32.dll that can be very helpful when working with a CLSID. They are declared as follows:

 Public Declare Function CLSIDFromProgID Lib "ole32.dll" (ByVal lpszProgID As Long, pClsid As GUID) As Long Public Declare Function CLSIDFromString Lib "ole32.dll" (ByVal lpszProgID As Long, pClsid As GUID) As Long Private Declare Function ProgIDFromCLSID Lib "ole32.dll" (pCLSID As GUID, lpszProgID As Long) As Long Public Declare Function StringFromCLSID Lib "ole32.dll" (pClsid As GUID, lpszProgID As Long) As Long 

These four functions allow you to use CLSIDs in almost any circumstance. The functions all take pointers to strings as parameters, so you will have to use StrPtr to provide these values. For the functions that return pointers to strings, use the StrFromPtrW function shown in this chapter.

Now we're ready to discuss GetUIObjectOf . The address for this method has been swapped in the vtable for a function called GetUIObjectOf X . This is done because we need to be able to return E_NOINTERFACE if the shell asks us for an interface that we cannot provide. Example 11.28 shows our implementation of GetUIObjectOf X .

Example 11.28. GetUIObjectOfX
  'DemoSpace.bas  Public Const IID_IExtractIconA = _      "{000214EB-0000-0000-C000-000000000046}" Public Const IID_IExtractIconW = _      "{000214FA-0000-0000-C000-000000000046}" Public Function GetUIObjectOfX(ByVal this As IShellFolder, _      ByVal hwndOwner As hWnd, ByVal cidl As UINT, _      aPidl As LPCITEMIDLIST, _      ByVal riid As REFIID, _      prgfInOut As UINT, ByVal ppvOut As LPVOID) As Long          GetUIObjectOfX = E_NOINTERFACE          Dim pUnk As IUnknownVB     Dim pidl As LPITEMIDLIST     Dim clsShellFolder As ShellFolder       Dim szIID As String         szIID = GetIID(riid) 

The first thing we need to do is figure out which interface the shell is asking for by calling GetIID with the riid parameter. Next, we will check to see if the interface is IExtractIconA (Windows 98) or IExtractIconW (NT). All other requests will be ignored:

 If (szIID = IID_IExtractIconA) Then                 Set clsShellFolder = this         Dim extIconA As ExtractIcon         Set extIconA = New ExtractIcon                  extIconA.pidl = aPidl                  Dim iExtIconA As IExtractIconA         Set iExtIconA = extIconA         Set pUnk = iExtIconA         pUnk.AddRef                  CopyMemory ByVal ppvOut, iExtIconA, 4         GetUIObjectOfX = S_OK              ElseIf (szIID = IID_IExtractIconW) Then 

Once it has been determined that the shell is asking for IExtractIcon , we can declare an instance of our ExtractIcon class (which implements both IExtractIconA and IExtractIconW ) and pass it the PIDL that was given to us by the shell through the aPidl parameter:

 Set clsShellFolder = this         Dim extIconW As ExtractIcon         Set extIconW = New ExtractIcon                          'Pass pidl to IExtractIcon.         extIconW.pidl = aPidl 

Next, we'll query our ExtractIcon class for the IExtractIcon itself. But before we pass the interface back to the shell, we need to do an AddRef so the interface will still be valid once this method terminates. We'll do that using IUnknownVB . Once this has been accomplished, the interface is copied to the address ppvOut :

 Dim iExtIconW As IExtractIconW         Set iExtIconW = extIconW         Set pUnk = iExtIconW         pUnk.AddRef                  CopyMemory ByVal ppvOut, iExtIconW, 4                  GetUIObjectOfX = S_OK              End If      End Function 

If all is well, the method should return S_OK .

11.6.8 ExtractIcon

ExtractIcon is a class we will use to provide the shell with icons for our namespace extension. You should already be familiar with IExtractIcon (see Chapter 5), so we will not discuss these interfaces again. But we will implement the interface a little bit differently than we did in Chapter 5. For one thing, our icons are not located in a file this time around. They will be stored in an image list. Therefore, we will implement Extract to provide the icons from an image list. GetIconLocation will only be used to specify the index of the icon in the image list.

If you recall from Example 11.28, we passed a PIDL to ExtractIcon. This is done through a property in the class called PIDL. This is shown in Example 11.29.

Example 11.29. ExtractIcon
 Implements IExtractIconA Implements IExtractIconW Private m_pidl As LPITEMIDLIST Private m_pidlMgr As pidlMgr Private m_pMalloc As IMalloc Private m_hWnd As Long Private Const ILD_TRANSPARENT = 1 Private Sub Class_Initialize(  )     Set m_pMalloc = GetMalloc     Set m_pidlMgr = New pidlMgr End Sub Private Sub Class_Terminate(  ) If (m_pidl > 0) Then         m_pidlMgr.Delete m_pidl     End If Set m_pidlMgr = Nothing End Sub Public Property Let pidl(ByVal p As LPITEMIDLIST)     m_  pidl = m_  pidlMgr.Copy(p) End Property 
11.6.8.1 GetIconLocation

GetIconLocation will only provide the index for the icon, not its location. Because of this, the shell will expect Extract to provide the actual location of the icon. Let's take a peek at GetIconLocation , which is shown in Example 11.30. We'll implement this method first, and then we'll tackle Extract . We'll only look at the IExtractIconW functions, but this should not be a problem. If you remember from Chapter 5, the difference between IExtractIconW and IExtractIconA is how the string containing the path to the icon must be handled. We will not be providing the shell a path for the icon location, so both interfaces can be implemented with the same code.

Example 11.30. IExtract::GetIconLocation
 Private Sub IExtractIconW_GetIconLocation( _      ByVal uFlags As VBShellLib.UINT, _      ByVal szIconFile As VBShellLib.LPWSTRVB, _      ByVal cchMax As VBShellLib.UINT, _      piIndex As Long, pwFlags As VBShellLib.GETICONLOCATIONRETURN)     pwFlags = GIL_NOTFILENAME     'pidl is either a value or a folder     Dim pidlTemp As LPITEMIDLIST          pidlTemp = m_pidlMgr.GetLastItem(m_pidl)          If m_pidlMgr.GetPidlType(pidlTemp) = PT_ITEM Then                  piIndex = ICON_ITEM              Else         If (uFlags And GIL_OPENICON) Then             piIndex = ICON_OPEN         Else             piIndex = ICON_CLOSED         End If     End If End Sub 

The first thing we need to do is get the last item in the PIDL. This makes sense if you think of the PIDL as a file path. You want to display an icon for the item at the end of the "path," not the beginning, right? Once we have the last item in the PIDL, we determine its type by calling PidlMgr::GetPidlType . Then we will know whether it is a "folder" or an "item."

If we have a folder, we need to check the uFlags parameter for GIL_OPENICON . This will tell us whether the folder is opened or closed.

The values denoted by ICON_ are merely constants describing the index of a particular icon in the image list that is located on frmView . They are simply:

 Public Const ICON_OPEN = 0 Public Const ICON_CLOSED = 1 Public Const ICON_ITEM = 2 
11.6.8.2 Extract

Extract is short and sweet. It is shown in Example 11.31.

Example 11.31. IExtractIcon::Extract
 Private Sub IExtractIconW_Extract( _      ByVal pszFile As VBShellLib.LPWSTRVB, _      ByVal nIconIndex As VBShellLib.UINT, _      phiconLarge As VBShellLib.HICON, _      phiconSmall As VBShellLib.HICON, _      ByVal nIconSize As VBShellLib.UINT)          Dim hImgList As Long     hImgList = frmView.ImageList.hImageList          phiconSmall = ImageList_GetIcon( _          hImgList, nIconIndex, ILD_TRANSPARENT)     phiconLarge = ImageList_GetIcon( _          hImgList, nIconIndex, ILD_TRANSPARENT) End Sub 

Since our view will never change, we only need to provide small icons to the shell. Therefore, phiconSmall and phiconLarge can both point to the same icons. The function ImageList_GetIcon , which actually returns the handle to the icons, is located in comctl32.dll and is declared as follows:

 Public Declare Function ImageList_GetIcon Lib "comctl32.dll" _      (ByVal himl As Long, ByVal i As Integer, ByVal flags As UINT) _      As HICON 

11.6.9 FillList

We need to get back to ShellView and finish it up. We have one function left that handles populating the view object with items, called FillList . Every namespace extension you write will have a FillList function, but as you can probably imagine, the implementation of this function is highly dependent on the format of the PIDL being used.

Here's how the function works. If you remember, we wrote a function called Initialize in ShellView which we used to pass in an instance of ShellFolder and a PIDL to the class. FillList relies on this instance of ShellFolder for calling EnumObjects. It will call EnumObjects, which will return an IEnumIDList interface. Once FillList has the IEnumIDList interface, it calls IEnumIDList::Next repeatedly in a loop until no more PIDLs are returned. As each PIDL is returned, we determine if the PIDL is a folder or an item and populate the list view accordingly . FillList is shown in Example 11.32. Let's go through it slowly.

Example 11.32. FillList
 Public Sub FillList(  )     Dim pEnumIDList As IEnumIDList     Dim pShellFolder As IShellFolder     Dim pidl As LPITEMIDLIST     Dim dwFetched As DWORD     Dim szPidlName As String     Dim lIconIndex As Long          Dim lv As ListView     Set lv = m_frmView.ListView          SendMessage lv.hWnd, LVM_DELETEALLITEMS, 0, 0 

The first significant thing that happens is that all the items in the list view are cleared. This is done using SendMessage in this example, but it doesn't have to be done this way. You could also call lv.ListItems.Clear . It's just a matter of personal preference:

 Dim hr As Long     Set pShellFolder = m_parentFolder      Set pEnumIDList = pShellFolder.EnumObjects _          (m_frmView.hWnd, SHCONTF_NONFOLDERS) 

Then we call IShellFolder::EnumObjects to get an IEnumIDList interface. This call is a little misleading here. It looks like we are asking for only non-folders (items), but this is not the case. If you examine CreateEnumList (Example 11.14), which is the function that builds the list for us, you will see that folders will always be added to the list (this is an implementation decision, not something that has to be done this way). By specifying SHCONTF_NONFOLDERS , we are asking for items in addition to folders:

 If (ObjPtr(pEnumIDList) > 0) Then                  'Turn off listview redraw         SendMessage lv.hWnd, WM_SETREDRAW, 0, 0                  pEnumIDList.Next 1, pidl, dwFetched 

After we have checked to make sure that our IEnumIDList interface is valid, SendMessage is used to turn off the drawing on the list view. This minimizes flickering when the list view is being populated with items. Once the drawing is off, we call IEnumIDList::Next for the first PIDL. IEnumIDList is interesting in this example, because it is the first (and only) time that we directly use an interface that has been implemented by us:

 Do While (dwFetched > 0)                      Dim li As ListItem             Dim pidlTemp As LPITEMIDLIST                          szPidlName = m_pidlMgr.GetPidlName(pidl)                          If (m_pidlMgr.GetPidlType(pidl) = PT_FOLDER) Then                 lIconIndex = 2             ElseIf (m_pidlMgr.GetPidlType(pidl) = PT_ITEM) Then                 lIconIndex = 3             End If 

When a PIDL has been returned to us, we get its display name and type. We could have called IShellFolder::GetDisplayNameOf for the name, but this would have been overkill. GetDisplayNameOf calls PidlMgr::GetPidlName , so we can just get to the point and call it ourselves .

The icon index is determined by the type. An interesting point to make here is that we ourselves are determining which icon should be displayed. Technically, we could call IShellFolder::GetUIObjectOf to get an IExtractIcon interface, and call GetIconLocation for this index value. Our implementation is so simple in this example, though, that it's easier just to do it this way.

Another reason that we do this ourselves is that we would have to write additional code to determine under which version of Windows we are running so we could return the appropriate interface ( IExtactIconA or IExtractIconW ):

 Set li = lv.ListItems.Add(, , szPidlName, , lIconIndex)             li.Tag = Str(pidl)                          pEnumIDList.Next 1, pidl, dwFetched                      Loop 

After the item is added to the list, we store the PIDL in the Tag property of the list item. This is done for one reason only. Our example will allow folder browsing from the view object. This means that when a folder item is double-clicked in the list view, navigation will take place. We need this PIDL so that we can determine whether a "folder" or an "item" was selected.

This happens repeatedly until there are no more PIDLs:

 'Turn on listview redraw         SendMessage lv.hWnd, WM_SETREDRAW, 1, 0              End If          Set pEnumIDList = Nothing      End Sub 

After the list view has been filled, drawing is turned back on, and our IEnumIDList interface is released.

11.6.10 Finishing the View Object

We have one more detail left on the view object. We need to allow for the browsing of folders. When a folder is double-clicked in the list view, we should navigate to that folder. Fortunately for us, this is a simple process. Everything is handled by IShellBrowser . To add this functionality, we add code for the DblClick event in list view. This code is shown in Example 11.33.

Example 11.33. ListView_DblClick
 Private Sub ListView_DblClick(  )     Dim pidl As LPITEMIDLIST     Dim li As ListItem     Set li = ListView.SelectedItem          pidl = CLng(li.Tag)          If (m_pidlMgr.GetPidlType(pidl) = PT_FOLDER) Then         m_pShellBrowser.BrowseObject pidl, SBSP_DEFBROWSER Or _                         SBSP_RELATIVE     End If               Set li = Nothing      End Sub 

The PIDL is extracted from the Tag property of the list item that was double-clicked and converted back to a Long. Its type is then determined. If the list item is a folder, IShellBrowser::BrowseObject is called. This has the same effect as if we were to double-click on the item in the tree view ourselves.

11.6.11 BindToObject

We have one more method left in IShellFolder that we need to implement. This method, BindToObject , is called when a folder is opened in the tree view. Its syntax is as follows:

 HRESULT BindToObject(LPCITEMIDLIST pidl, LPBC pbcReserved,                       REFIID riid,LPVOID *ppvOut); 

BindToObject acts similarly to QueryInterface. The shell will ask for an interface via the riid parameter, and it will be our job to provide the interface through ppvOut . In fact, we'll just forward this request to IUnknownVB::QueryInterface . Under Windows 9x and Windows NT, the shell will always ask for IShellFolder . But not so for Windows 2000. In the event that the shell asks for another interface, we need to be prepared to return E_OUTOFMEMORY which is why this method has been swapped in the vtable with a replacement function. The remaining code for the ShellFolder class in shown in Example 11.34.

Example 11.34. IShellFolder::BindToObject
 'Demospace.bas Public Const IID_IShellFolder = _               "{000214E6-0000-0000-C000-000000000046}" Public Function BindToObjectX(ByVal this As IShellFolder, _                                ByVal pidl As LPCITEMIDLIST, _                                ByVal pbcReserved As LPBC, _                                ByVal riid As REFIID, _                                ppvOut As LPVOID) As Long     BindToObjectX = E_OUTOFMEMORY     Dim iid As String         iid = GetIID(riid)     If iid = IID_IShellFolder Then         'Current shell folder.         Dim pParentShellFolder As ShellFolder         Set pParentShellFolder = this              'New child shell folder.         Dim pShellFolder As ShellFolder         Set pShellFolder = New ShellFolder              'Pass current pidl and folder reference.         'to child folder         Set pShellFolder.Parent = pParentShellFolder         pShellFolder.pidl = pidl              'Give new shell folder back to the shell.         Dim pUnk As IUnknownVB         Set pUnk = pShellFolder              pUnk.QueryInterface riid, ppvOut              Set pUnk = Nothing         Set pShellFolder = Nothing         Set pParentShellFolder = Nothing              BindToObjectX = S_OK     End If End Function  'ShellFolder.cls Public Property Set Parent(p As ShellFolder)     Set m_parentFolder = p End Property Public Property Let pidl(ByVal p As LPITEMIDLIST)          m_pidl = m_pidlMgr.Copy(p)     Dim pidlTemp As LPITEMIDLIST         pidlTemp = m_pidlMgr.GetLastItem(m_pidl)          m_iLevel = m_pidlMgr.GetPidlLevel(pidlTemp) + 1      End Property 

When BindToObject terminates, the IShellFolder interface that we have passed back to the shell is still valid even though we have not called AddRef implicitly. This is because QueryInterface calls AddRef for us. In fact, this behavior is a rule for properly implementing QueryInterface.

Basically, opening another folder repeats the entire process we have just walked through. When BindToObject is called, we create an instance of ShellFolder ourselves, passing it a reference to the current ShellFolder and the current PIDL. The IShellFolder interface is then given back to the shell, and the process begins again.

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