Click here to Skip to main content
15,886,104 members
Articles / Programming Languages / Visual Basic

Explorer ComboBox and ListView in VB.NET

Rate me:
Please Sign up or sign in to vote.
4.88/5 (53 votes)
18 Aug 2006CPOL18 min read 1M   13.2K   141  
A ComboBox (and ListView) displaying the file system, using the system image list.
Imports System.IO
Imports System.IO.FileSystemInfo
Imports System.Runtime.InteropServices
Imports System.Text
Imports ExplorerControls.ShellDll


Public Class CShItem
    Implements IDisposable, IComparable

#Region "   Shared Private Fields"
    'This class has occasion to refer to the TypeName as reported by
    ' SHGetFileInfo. It needs to compare this to the string
    ' (in English) "System Folder"
    'on non-English systems, we do not know, in the general case,
    ' what the equivalent string is to compare against
    'The following variable is set by Sub New() to the string that
    ' corresponds to "System Folder" on the current machine
    ' Sub New() depends on the existance of My Computer(CSIDL.DRIVES),
    ' to determine what the equivalent string is
    Private Shared m_strSystemFolder As String

    'My Computer is also commonly used (though not internally),
    ' so save & expose its name on the current machine
    Private Shared m_strMyComputer As String

    'To get My Documents sorted first, we need to know the Locale 
    'specific name of that folder.
    Private Shared m_strMyDocuments As String

    ' The DesktopBase is set up via Sub New() (one time only) and
    '  disposed of only when DesktopBase is finally disposed of
    Private Shared DesktopBase As CShItem

    'We can avoid an extra SHGetFileInfo call once this is set up
    Private Shared OpenFolderIconIndex As Integer = -1

    ' It is also useful to know if the OS is XP or above.  
    ' Set up in Sub New() to avoid multiple calls to find this info
    Private Shared XPorAbove As Boolean
    ' Likewise if OS is Win2K or Above
    Private Shared Win2KOrAbove As Boolean

    ' DragDrop, possibly among others, needs to know the Path of
    ' the DeskTopDirectory in addition to the Desktop itself
    ' Also need the actual CShItem for the DeskTopDirectory, so get it
    Private Shared m_DeskTopDirectory As CShItem


#End Region

#Region "   Instance Private Fields"
    'm_Folder and m_Pidl must be released/freed at Dispose time
    Private m_Folder As IShellFolder    'if item is a folder, contains the Folder interface for this instance
    Private m_Pidl As IntPtr            'The Absolute PIDL for this item (not retained for files)
    Private m_DisplayName As String = ""
    Private m_Path As String
    Private m_TypeName As String
    Private m_Parent As CShItem '= Nothing
    Private m_IconIndexNormal As Integer   'index into the System Image list for Normal icon
    Private m_IconIndexOpen As Integer 'index into the SystemImage list for Open icon
    Private m_IsBrowsable As Boolean
    Private m_IsFileSystem As Boolean
    Private m_IsFolder As Boolean
    Private m_HasSubFolders As Boolean
    Private m_IsLink As Boolean
    Private m_IsDisk As Boolean
    Private m_IsShared As Boolean
    Private m_IsHidden As Boolean
    Private m_IsNetWorkDrive As Boolean '= False
    Private m_IsRemovable As Boolean '= False
    Private m_IsReadOnly As Boolean '= False
    'Properties of interest to Drag Operations
    Private m_CanMove As Boolean '= False
    Private m_CanCopy As Boolean '= False
    Private m_CanDelete As Boolean '= False
    Private m_CanLink As Boolean '= False
    Private m_IsDropTarget As Boolean '= False
    Private m_Attributes As SFGAO       'the original, returned from GetAttributesOf

    Private m_SortFlag As Integer '= 0 'Used in comparisons

    Private m_Directories As ArrayList

    'The following elements are only filled in on demand
    Private m_XtrInfo As Boolean '= False
    Private m_LastWriteTime As DateTime
    Private m_CreationTime As DateTime
    Private m_LastAccessTime As DateTime
    Private m_Length As Long

    'Indicates whether DisplayName, TypeName, SortFlag have been set up
    Private m_HasDispType As Boolean '= False

    'Indicates whether IsReadOnly has been set up
    Private m_IsReadOnlySetup As Boolean '= False

    'Holds a byte() representation of m_PIDL -- filled when needed
    Private m_cPidl As cPidl

    'Flags for Dispose state
    Private m_IsDisposing As Boolean
    Private m_Disposed As Boolean

#End Region

#Region "   Destructor"
    ''' <summary>
    ''' Summary of Dispose.
    ''' </summary>
    ''' 
    Public Overloads Sub Dispose() Implements IDisposable.Dispose
        Dispose(True)
        ' Take yourself off of the finalization queue
        ' to prevent finalization code for this object
        ' from executing a second time.
        GC.SuppressFinalize(Me)
    End Sub
    ''' <summary>
    ''' Deallocates CoTaskMem contianing m_Pidl and removes reference to m_Folder
    ''' </summary>
    ''' <param name="disposing"></param>
    ''' 
    Protected Overridable Overloads Sub Dispose(ByVal disposing As Boolean)
        ' Allow your Dispose method to be called multiple times,
        ' but throw an exception if the object has been disposed.
        ' Whenever you do something with this class, 
        ' check to see if it has been disposed.
        If Not (m_Disposed) Then
            ' If disposing equals true, dispose all managed 
            ' and unmanaged resources.
            m_Disposed = True
            If (disposing) Then
            End If
            ' Release unmanaged resources. If disposing is false,
            ' only the following code is executed. 
            If Not IsNothing(m_Folder) Then
                Marshal.ReleaseComObject(m_Folder)
            End If
            If Not m_Pidl.Equals(IntPtr.Zero) Then
                Marshal.FreeCoTaskMem(m_Pidl)
            End If
        Else
            Throw New Exception("CShItem Disposed more than once")
        End If
    End Sub

    ' This Finalize method will run only if the 
    ' Dispose method does not get called.
    ' By default, methods are NotOverridable. 
    ' This prevents a derived class from overriding this method.
    ''' <summary>
    ''' Summary of Finalize.
    ''' </summary>
    ''' 
    Protected Overrides Sub Finalize()
        ' Do not re-create Dispose clean-up code here.
        ' Calling Dispose(false) is optimal in terms of
        ' readability and maintainability.
        Dispose(False)
    End Sub

#End Region

#Region "   Constructors"

#Region "       Private Sub New(ByVal folder As IShellFolder, ByVal pidl As IntPtr, ByVal parent As CShItem)"
    ''' <summary>
    ''' Private Constructor, creates new CShItem from the item's parent folder and
    '''  the item's PIDL relative to that folder.</summary>
    ''' <param name="folder">the folder interface of the parent</param>
    ''' <param name="pidl">the Relative PIDL of this item</param>
    ''' <param name="parent">the CShitem of the parent</param>
    ''' 
    Private Sub New(ByVal folder As IShellFolder, ByVal pidl As IntPtr, ByVal parent As CShItem)
        If IsNothing(DesktopBase) Then
            DesktopBase = New CShItem() 'This initializes the Desktop folder
        End If
        m_Parent = parent
        m_Pidl = concatPidls(parent.PIDL, pidl)

        'Get some attributes
        SetUpAttributes(folder, pidl)

        'Set unfetched value for IconIndex....
        m_IconIndexNormal = -1
        m_IconIndexOpen = -1
        'finally, set up my Folder
        If m_IsFolder Then
            Dim HR As Integer
            HR = folder.BindToObject(pidl, IntPtr.Zero, IID_IShellFolder, m_Folder)
            If HR <> NOERROR Then
                Marshal.ThrowExceptionForHR(HR)
            End If
        End If
    End Sub
#End Region

#Region "       Sub New()"
    ''' <summary>
    ''' Private Constructor. Creates CShItem of the Desktop
    ''' </summary>
    ''' 
    Private Sub New()           'only used when desktopfolder has not been intialized
        If Not IsNothing(DesktopBase) Then
            Throw New Exception("Attempt to initialize CShItem for second time")
        End If

        Dim HR As Integer
        'firstly determine what the local machine calls a "System Folder" and "My Computer"
        Dim tmpPidl As IntPtr
        HR = SHGetSpecialFolderLocation(0, CSIDL.DRIVES, tmpPidl)
        Dim shfi As New SHFILEINFO()
        Dim dwflag As Integer = SHGFI.DISPLAYNAME Or _
                                SHGFI.TYPENAME Or _
                                SHGFI.PIDL
        Dim dwAttr As Integer = 0
        SHGetFileInfo(tmpPidl, dwAttr, shfi, cbFileInfo, dwflag)
        m_strSystemFolder = shfi.szTypeName
        m_strMyComputer = shfi.szDisplayName
        Marshal.FreeCoTaskMem(tmpPidl)
        'set OS version info
        XPorAbove = ShellDll.IsXpOrAbove()
        Win2KOrAbove = ShellDll.Is2KOrAbove

        'With That done, now set up Desktop CShItem
        m_Path = "::{" & DesktopGUID.ToString & "}"
        m_IsFolder = True
        m_HasSubFolders = True
        m_IsBrowsable = False
        HR = SHGetDesktopFolder(m_Folder)
        m_Pidl = GetSpecialFolderLocation(IntPtr.Zero, CSIDL.DESKTOP)
        dwflag = SHGFI.DISPLAYNAME Or _
                 SHGFI.TYPENAME Or _
                 SHGFI.SYSICONINDEX Or _
                 SHGFI.PIDL
        dwAttr = 0
        Dim H As IntPtr = SHGetFileInfo(m_Pidl, dwAttr, shfi, cbFileInfo, dwflag)
        m_DisplayName = shfi.szDisplayName
        m_TypeName = strSystemFolder   'not returned correctly by SHGetFileInfo
        m_IconIndexNormal = shfi.iIcon
        m_IconIndexOpen = shfi.iIcon
        m_HasDispType = True
        m_IsDropTarget = True
        m_IsReadOnly = False
        m_IsReadOnlySetup = True

        'also get local name for "My Documents"
        Dim pchEaten As Integer
        tmpPidl = IntPtr.Zero
        HR = m_Folder.ParseDisplayName(Nothing, Nothing, "::{450d8fba-ad25-11d0-98a8-0800361b1103}", _
                 pchEaten, tmpPidl, Nothing)
        shfi = New SHFILEINFO()
        dwflag = SHGFI.DISPLAYNAME Or _
                                SHGFI.TYPENAME Or _
                                SHGFI.PIDL
        dwAttr = 0
        SHGetFileInfo(tmpPidl, dwAttr, shfi, cbFileInfo, dwflag)
        m_strMyDocuments = shfi.szDisplayName
        Marshal.FreeCoTaskMem(tmpPidl)
        'this must be done after getting "My Documents" string
        m_SortFlag = ComputeSortFlag()
        'Set DesktopBase
        DesktopBase = Me
        ' Lastly, get the Path and CShItem of the DesktopDirectory -- useful for DragDrop
        m_DeskTopDirectory = New CShItem(CSIDL.DESKTOPDIRECTORY)
    End Sub
#End Region

#Region "       New(ByVal ID As CSIDL)"
    ''' <summary>Create instance based on a non-desktop CSIDL.
    ''' Will create based on any CSIDL Except the DeskTop CSIDL</summary>
    ''' <param name="ID">Value from CSIDL enumeration denoting the folder to create this CShItem of.</param>
    ''' 
    Sub New(ByVal ID As CSIDL)
        If IsNothing(DesktopBase) Then
            DesktopBase = New CShItem() 'This initializes the Desktop folder
        End If
        Dim HR As Integer
        If ID = CSIDL.MYDOCUMENTS Then
            Dim pchEaten As Integer
            HR = DesktopBase.m_Folder.ParseDisplayName(Nothing, Nothing, "::{450d8fba-ad25-11d0-98a8-0800361b1103}", _
                     pchEaten, m_Pidl, Nothing)
        Else
            HR = SHGetSpecialFolderLocation(0, ID, m_Pidl)
        End If
        If HR = NOERROR Then
            Dim pParent As IShellFolder
            Dim relPidl As IntPtr = IntPtr.Zero

            pParent = GetParentOf(m_Pidl, relPidl)
            'Get the Attributes
            SetUpAttributes(pParent, relPidl)
            'Set unfetched value for IconIndex....
            m_IconIndexNormal = -1
            m_IconIndexOpen = -1
            'finally, set up my Folder
            If m_IsFolder Then
                HR = pParent.BindToObject(relPidl, IntPtr.Zero, IID_IShellFolder, m_Folder)
                If HR <> NOERROR Then
                    Marshal.ThrowExceptionForHR(HR)
                End If
            End If
            Marshal.ReleaseComObject(pParent)
            'if PidlCount(m_Pidl) = 1 then relPidl is same as m_Pidl, don't release
            If PidlCount(m_Pidl) > 1 Then Marshal.FreeCoTaskMem(relPidl)
        Else
            Marshal.ThrowExceptionForHR(HR)
        End If
    End Sub
#End Region

#Region "       New(ByVal path As String)"
    ''' <summary>Create a new CShItem based on a Path Must be a valid FileSystem Path</summary>
    ''' <param name="path"></param>
    ''' 
    Sub New(ByVal path As String)
        If IsNothing(DesktopBase) Then
            DesktopBase = New CShItem() 'This initializes the Desktop folder
        End If
        'Removal of following code allows Path(GUID) of Special FOlders to serve
        '  as a valid Path for CShItem creation (part of Calum's refresh code needs this
        'If Not Directory.Exists(path) AndAlso Not File.Exists(path) Then
        '    Throw New Exception("CShItem -- Invalid Path specified")
        'End If
        Dim HR As Integer
        HR = DesktopBase.m_Folder.ParseDisplayName(0, IntPtr.Zero, path, 0, m_Pidl, 0)
        If Not HR = NOERROR Then Marshal.ThrowExceptionForHR(HR)
        Dim pParent As IShellFolder
        Dim relPidl As IntPtr = IntPtr.Zero

        pParent = GetParentOf(m_Pidl, relPidl)

        'Get the Attributes
        SetUpAttributes(pParent, relPidl)
        'Set unfetched value for IconIndex....
        m_IconIndexNormal = -1
        m_IconIndexOpen = -1
        'finally, set up my Folder
        If m_IsFolder Then
            HR = pParent.BindToObject(relPidl, IntPtr.Zero, IID_IShellFolder, m_Folder)
            If HR <> NOERROR Then
                Marshal.ThrowExceptionForHR(HR)
            End If
        End If
        Marshal.ReleaseComObject(pParent)
        'if PidlCount(m_Pidl) = 1 then relPidl is same as m_Pidl, don't release
        If PidlCount(m_Pidl) > 1 Then
            Marshal.FreeCoTaskMem(relPidl)
        End If
    End Sub
#End Region

#Region "       New(ByVal FoldBytes() as Byte, ByVal ItemBytes() as Byte)"
    '''<Summary>Given a Byte() containing the Pidl of the parent
    ''' folder and another Byte() containing the Pidl of the Item,
    ''' relative to the Folder, Create a CShItem for the Item.
    ''' This is of primary use in dealing with "Shell IDList Array" 
    ''' formatted info passed in a Drag Operation
    ''' </Summary>
    Sub New(ByVal FoldBytes() As Byte, ByVal ItemBytes() As Byte)
        Debug.WriteLine("CShItem.New(FoldBytes,ItemBytes) Fold len= " & FoldBytes.Length & " Item Len = " & ItemBytes.Length)
        If IsNothing(DesktopBase) Then
            DesktopBase = New CShItem() 'This initializes the Desktop folder
        End If
        Dim pParent As IShellFolder = MakeFolderFromBytes(FoldBytes)
        If IsNothing(pParent) Then
            GoTo XIT    'm_PIDL will = IntPtr.Zero for really bad CShitem
        End If
        Dim ipParent As IntPtr = cPidl.BytesToPidl(FoldBytes)
        Dim ipItem As IntPtr = cPidl.BytesToPidl(ItemBytes)
        If ipParent.Equals(IntPtr.Zero) Or ipItem.Equals(IntPtr.Zero) Then
            GoTo XIT
        End If
        ' Now process just like sub new(folder,pidl,parent) version
        m_Pidl = concatPidls(ipParent, ipItem)

        'Get some attributes
        SetUpAttributes(pParent, ipItem)

        'Set unfetched value for IconIndex....
        m_IconIndexNormal = -1
        m_IconIndexOpen = -1
        'finally, set up my Folder
        If m_IsFolder Then
            Dim HR As Integer
            HR = pParent.BindToObject(ipItem, IntPtr.Zero, IID_IShellFolder, m_Folder)
#If Debug Then
            If HR <> NOERROR Then
                Marshal.ThrowExceptionForHR(HR)
            End If
#End If
        End If
XIT:    'On any kind of exit, free the allocated memory
#If Debug Then
        If m_Pidl.Equals(IntPtr.Zero) Then
            Debug.WriteLine("CShItem.New(FoldBytes,ItemBytes) Failed")
        Else
            Debug.WriteLine("CShItem.New(FoldBytes,ItemBytes) Created " & Me.Path)
        End If
#End If
        If Not ipParent.Equals(IntPtr.Zero) Then
            Marshal.FreeCoTaskMem(ipParent)
        End If
        If Not ipItem.Equals(IntPtr.Zero) Then
            Marshal.FreeCoTaskMem(ipItem)
        End If
    End Sub

#End Region

#Region "       Utility functions used in Constructors"

#Region "       IsValidPidl"
    '''<Summary>It is impossible to validate a PIDL completely since its contents
    ''' are arbitrarily defined by the creating Shell Namespace.  However, it
    ''' is possible to validate the structure of a PIDL.</Summary>
    Public Shared Function IsValidPidl(ByVal b() As Byte) As Boolean
        IsValidPidl = False     'assume failure
        Dim bMax As Integer = b.Length - 1   'max value that index can have
        If bMax < 1 Then Exit Function 'min size is 2 bytes
        Dim cb As Integer = b(0) + (b(1) * 256)
        Dim indx As Integer = 0
        Do While cb > 0
            If (indx + cb + 1) > bMax Then Exit Function 'an error
            indx += cb
            cb = b(indx) + (b(indx + 1) * 256)
        Loop
        ' on fall thru, it is ok as far as we can check
        IsValidPidl = True
    End Function
#End Region

#Region "   MakeFolderFromBytes"
    Public Shared Function MakeFolderFromBytes(ByVal b As Byte()) As ShellDll.IShellFolder
        MakeFolderFromBytes = Nothing       'get rid of VS2005 warning
        If Not IsValidPidl(b) Then Return Nothing
        If b.Length = 2 AndAlso ((b(0) = 0) And (b(1) = 0)) Then 'this is the desktop
            Return DesktopBase.Folder
        ElseIf b.Length = 0 Then   'Also indicates the desktop
            Return DesktopBase.Folder
        Else
            Dim ptr As IntPtr = Marshal.AllocCoTaskMem(b.Length)
            If ptr.Equals(IntPtr.Zero) Then Return Nothing
            Marshal.Copy(b, 0, ptr, b.Length)
            'the next statement assigns a IshellFolder object to the function return, or has an error
            Dim hr As Integer = DesktopBase.Folder.BindToObject(ptr, IntPtr.Zero, IID_IShellFolder, MakeFolderFromBytes)
            If hr <> 0 Then MakeFolderFromBytes = Nothing
            Marshal.FreeCoTaskMem(ptr)
        End If
    End Function
#End Region

#Region "           GetParentOf"

    'UPDATE: Set accessor to friend

    '''<Summary>Returns both the IShellFolder interface of the parent folder
    '''  and the relative pidl of the input PIDL</Summary>
    '''<remarks>Several internal functions need this information and do not have
    ''' it readily available. GetParentOf serves those functions</remarks>
    Friend Shared Function GetParentOf(ByVal pidl As IntPtr, ByRef relPidl As IntPtr) As IShellFolder
        GetParentOf = Nothing     'avoid VB2005 warning
        Dim HR As Integer
        Dim itemCnt As Integer = PidlCount(pidl)
        If itemCnt = 1 Then         'parent is desktop
            HR = SHGetDesktopFolder(GetParentOf)
            relPidl = pidl
        Else
            Dim tmpPidl As IntPtr
            tmpPidl = TrimPidl(pidl, relPidl)
            HR = DesktopBase.m_Folder.BindToObject(tmpPidl, IntPtr.Zero, IID_IShellFolder, GetParentOf)
            Marshal.FreeCoTaskMem(tmpPidl)
        End If
        If Not HR = NOERROR Then Marshal.ThrowExceptionForHR(HR)
    End Function
#End Region

#Region "           SetUpAttributes"
    ''' <summary>Get the base attributes of the folder/file that this CShItem represents</summary>
    ''' <param name="folder">Parent Folder of this Item</param>
    ''' <param name="pidl">Relative Pidl of this Item.</param>
    ''' 
    Private Sub SetUpAttributes(ByVal folder As IShellFolder, ByVal pidl As IntPtr)
        Dim attrFlag As SFGAO
        attrFlag = SFGAO.BROWSABLE
        attrFlag = attrFlag Or SFGAO.FILESYSTEM
        attrFlag = attrFlag Or SFGAO.HASSUBFOLDER
        attrFlag = attrFlag Or SFGAO.FOLDER
        attrFlag = attrFlag Or SFGAO.LINK
        attrFlag = attrFlag Or SFGAO.SHARE
        attrFlag = attrFlag Or SFGAO.HIDDEN
        attrFlag = attrFlag Or SFGAO.REMOVABLE
        'attrFlag = attrFlag Or SFGAO.RDONLY   'made into an on-demand attribute
        attrFlag = attrFlag Or SFGAO.CANCOPY
        attrFlag = attrFlag Or SFGAO.CANDELETE
        attrFlag = attrFlag Or SFGAO.CANLINK
        attrFlag = attrFlag Or SFGAO.CANMOVE
        attrFlag = attrFlag Or SFGAO.DROPTARGET
        'Note: for GetAttributesOf, we must provide an array, in  all cases with 1 element
        Dim aPidl(0) As IntPtr
        aPidl(0) = pidl
        folder.GetAttributesOf(1, aPidl, attrFlag)
        m_Attributes = attrFlag
        m_IsBrowsable = CBool(attrFlag And SFGAO.BROWSABLE)
        m_IsFileSystem = CBool(attrFlag And SFGAO.FILESYSTEM)
        m_HasSubFolders = CBool(attrFlag And SFGAO.HASSUBFOLDER)
        m_IsFolder = CBool(attrFlag And SFGAO.FOLDER)
        m_IsLink = CBool(attrFlag And SFGAO.LINK)
        m_IsShared = CBool(attrFlag And SFGAO.SHARE)
        m_IsHidden = CBool(attrFlag And SFGAO.HIDDEN)
        m_IsRemovable = CBool(attrFlag And SFGAO.REMOVABLE)
        'm_IsReadOnly = CBool(attrFlag And SFGAO.RDONLY)      'made into an on-demand attribute
        m_CanCopy = CBool(attrFlag And SFGAO.CANCOPY)
        m_CanDelete = CBool(attrFlag And SFGAO.CANDELETE)
        m_CanLink = CBool(attrFlag And SFGAO.CANLINK)
        m_CanMove = CBool(attrFlag And SFGAO.CANMOVE)
        m_IsDropTarget = CBool(attrFlag And SFGAO.DROPTARGET)

        'Get the Path
        Dim strr As IntPtr = Marshal.AllocCoTaskMem(MAX_PATH * 2 + 4)
        Marshal.WriteInt32(strr, 0, 0)
        Dim buf As New StringBuilder(MAX_PATH)
        Dim itemflags As SHGDN = SHGDN.FORPARSING
        folder.GetDisplayNameOf(pidl, itemflags, strr)
        Dim HR As Integer = StrRetToBuf(strr, pidl, buf, MAX_PATH)
        Marshal.FreeCoTaskMem(strr)     'now done with it
        If HR = NOERROR Then
            m_Path = buf.ToString
            'check for zip file = folder on xp, leave it a file
            If m_IsFolder AndAlso m_IsFileSystem AndAlso XPorAbove Then
                'Note:meaning of SFGAO.STREAM changed between win2k and winXP
                'Version 20 code
                'If File.Exists(m_Path) Then
                '    m_IsFolder = False
                'End If
                'Version 21 code
                aPidl(0) = pidl
                attrFlag = SFGAO.STREAM
                folder.GetAttributesOf(1, aPidl, attrFlag)
                If attrFlag And SFGAO.STREAM Then
                    m_IsFolder = False
                End If
            End If
            If m_Path.Length = 3 AndAlso m_Path.Substring(1).Equals(":\") Then
                m_IsDisk = True
            End If
        Else
            Marshal.ThrowExceptionForHR(HR)
        End If
    End Sub

#End Region

#End Region

#Region "       Public Shared Function GetCShItem(ByVal path As String) As CShItem"
    Public Shared Function GetCShItem(ByVal path As String) As CShItem
        GetCShItem = Nothing    'assume failure
        Dim HR As Integer
        Dim tmpPidl As IntPtr
        HR = GetDeskTop.Folder.ParseDisplayName(0, IntPtr.Zero, path, 0, tmpPidl, 0)
        If HR = 0 Then
            GetCShItem = FindCShItem(tmpPidl)
            If IsNothing(GetCShItem) Then
                Try
                    GetCShItem = New CShItem(path)
                Catch
                    GetCShItem = Nothing
                End Try
            End If
        End If
        If Not tmpPidl.Equals(IntPtr.Zero) Then
            Marshal.FreeCoTaskMem(tmpPidl)
        End If
    End Function
#End Region

#Region "       Public Shared Function GetCShItem(ByVal ID As CSIDL) As CShItem"
    Public Shared Function GetCShItem(ByVal ID As CSIDL) As CShItem
        GetCShItem = Nothing      'avoid VB2005 Warning
        If ID = CSIDL.DESKTOP Then
            Return GetDeskTop()
        End If
        Dim HR As Integer
        Dim tmpPidl As IntPtr
        If ID = CSIDL.MYDOCUMENTS Then
            Dim pchEaten As Integer
            HR = GetDeskTop.Folder.ParseDisplayName(Nothing, Nothing, "::{450d8fba-ad25-11d0-98a8-0800361b1103}", _
                     pchEaten, tmpPidl, Nothing)
        Else
            HR = SHGetSpecialFolderLocation(0, ID, tmpPidl)
        End If
        If HR = NOERROR Then
            GetCShItem = FindCShItem(tmpPidl)
            If IsNothing(GetCShItem) Then
                Try
                    GetCShItem = New CShItem(ID)
                Catch
                    GetCShItem = Nothing
                End Try
            End If
        End If
        If Not tmpPidl.Equals(IntPtr.Zero) Then
            Marshal.FreeCoTaskMem(tmpPidl)
        End If
    End Function
#End Region

#Region "       Public Shared Function GetCShItem(ByVal FoldBytes() As Byte, ByVal ItemBytes() As Byte) As CShItem"
    Public Shared Function GetCShItem(ByVal FoldBytes() As Byte, ByVal ItemBytes() As Byte) As CShItem
        GetCShItem = Nothing    'assume failure
        Dim b() As Byte = cPidl.JoinPidlBytes(FoldBytes, ItemBytes)
        If IsNothing(b) Then Exit Function 'can do no more with invalid pidls
        'otherwise do like below, skipping unnecessary validation check
        Dim thisPidl As IntPtr = Marshal.AllocCoTaskMem(b.Length)
        If thisPidl.Equals(IntPtr.Zero) Then Return Nothing
        Marshal.Copy(b, 0, thisPidl, b.Length)
        GetCShItem = FindCShItem(thisPidl)
        Marshal.FreeCoTaskMem(thisPidl)
        If IsNothing(GetCShItem) Then   'didn't find it, make new
            Try
                GetCShItem = New CShItem(FoldBytes, ItemBytes)
            Catch

            End Try
        End If
        If GetCShItem.PIDL.Equals(IntPtr.Zero) Then GetCShItem = Nothing
    End Function
#End Region

#Region "       Public Shared Function FindCShItem(ByVal b() As Byte) As CShItem"
    Public Shared Function FindCShItem(ByVal b() As Byte) As CShItem
        If Not IsValidPidl(b) Then Return Nothing
        Dim thisPidl As IntPtr = Marshal.AllocCoTaskMem(b.Length)
        If thisPidl.Equals(IntPtr.Zero) Then Return Nothing
        Marshal.Copy(b, 0, thisPidl, b.Length)
        FindCShItem = FindCShItem(thisPidl)
        Marshal.FreeCoTaskMem(thisPidl)
    End Function
#End Region

#Region "       Public Shared Function FindCShItem(ByVal ptr As IntPtr) As CShItem"
    Public Shared Function FindCShItem(ByVal ptr As IntPtr) As CShItem
        FindCShItem = Nothing     'avoid VB2005 Warning
        Dim BaseItem As CShItem = CShItem.GetDeskTop
        Dim CSI As CShItem
        Dim FoundIt As Boolean = False      'True if we found item or an ancestor
        Do Until FoundIt
            For Each CSI In BaseItem.GetDirectories
                If IsAncestorOf(CSI.PIDL, ptr) Then
                    If CShItem.IsEqual(CSI.PIDL, ptr) Then  'we found the desired item
                        Return CSI
                    Else
                        BaseItem = CSI
                        FoundIt = True
                        Exit For
                    End If
                End If
            Next
            If Not FoundIt Then Return Nothing 'didn't find an ancestor
            'The complication is that the desired item may not be a directory
            If Not IsAncestorOf(BaseItem.PIDL, ptr, True) Then  'Don't have immediate ancestor
                FoundIt = False     'go around again
            Else
                For Each CSI In BaseItem.GetItems
                    If CShItem.IsEqual(CSI.PIDL, ptr) Then
                        Return CSI
                    End If
                Next
                'fall thru here means it doesn't exist or we can't find it because of funny PIDL from SHParseDisplayName
                Return Nothing
            End If
        Loop
    End Function
#End Region

#End Region

#Region "   Icomparable -- for default Sorting"

    ''' <summary>Computes the Sort key of this CShItem, based on its attributes</summary>
    ''' 
    Private Function ComputeSortFlag() As Integer
        Dim rVal As Integer = 0
        If m_IsDisk Then rVal = &H100000
        If m_TypeName.Equals(strSystemFolder) Then
            If Not m_IsBrowsable Then
                rVal = rVal Or &H10000
                If m_strMyDocuments.Equals(m_DisplayName) Then
                    rVal = rVal Or &H1
                End If
            Else
                rVal = rVal Or &H1000
            End If
        End If
        If m_IsFolder Then rVal = rVal Or &H100
        Return rVal
    End Function

    '''<Summary> CompareTo(obj as object)
    '''  Compares obj to this instance based on SortFlag-- obj must be a CShItem</Summary>
    '''<SortOrder>  (low)Disks,non-browsable System Folders,
    '''              browsable System Folders, 
    '''               Directories, Files, Nothing (high)</SortOrder>
    Public Overridable Overloads Function CompareTo(ByVal obj As Object) As Integer _
            Implements IComparable.CompareTo
        If IsNothing(obj) Then Return 1 'non-existant is always low
        Dim Other As CShItem = DirectCast(obj, CShItem)
        If Not m_HasDispType Then SetDispType()
        Dim cmp As Integer = Other.SortFlag - m_SortFlag 'Note the reversal
        If cmp <> 0 Then
            Return cmp
        Else
            If m_IsDisk Then 'implies that both are
                Return String.Compare(m_Path, Other.Path)
            Else
                Return String.Compare(m_DisplayName, Other.DisplayName)
            End If
        End If
    End Function
#End Region

#Region "   Properties"

#Region "       Shared Properties"
    Public Shared ReadOnly Property strMyComputer() As String
        Get
            Return m_strMyComputer
        End Get
    End Property

    Public Shared ReadOnly Property strSystemFolder() As String
        Get
            Return m_strSystemFolder
        End Get
    End Property

    Public Shared ReadOnly Property DesktopDirectoryPath() As String
        Get
            Return m_DeskTopDirectory.Path
        End Get
    End Property

#End Region

#Region "       Normal Properties"
    Public ReadOnly Property PIDL() As IntPtr
        Get
            Return m_Pidl
        End Get
    End Property

    Public ReadOnly Property Folder() As IShellFolder
        Get
            Return m_Folder
        End Get
    End Property

    Public ReadOnly Property Path() As String
        Get
            Return m_Path
        End Get
    End Property
    Public ReadOnly Property Parent() As CShItem
        Get
            Return m_Parent
        End Get
    End Property

    Public ReadOnly Property Attributes() As SFGAO
        Get
            Return m_Attributes
        End Get
    End Property
    Public ReadOnly Property IsBrowsable() As Boolean
        Get
            Return m_IsBrowsable
        End Get
    End Property
    Public ReadOnly Property IsFileSystem() As Boolean
        Get
            Return m_IsFileSystem
        End Get
    End Property
    Public ReadOnly Property IsFolder() As Boolean
        Get
            Return m_IsFolder
        End Get
    End Property
    Public ReadOnly Property HasSubFolders() As Boolean
        Get
            Return m_HasSubFolders
        End Get
    End Property
    Public ReadOnly Property IsDisk() As Boolean
        Get
            Return m_IsDisk
        End Get
    End Property
    Public ReadOnly Property IsLink() As Boolean
        Get
            Return m_IsLink
        End Get
    End Property
    Public ReadOnly Property IsShared() As Boolean
        Get
            Return m_IsShared
        End Get
    End Property
    Public ReadOnly Property IsHidden() As Boolean
        Get
            Return m_IsHidden
        End Get
    End Property
    Public ReadOnly Property IsRemovable() As Boolean
        Get
            Return m_IsRemovable
        End Get
    End Property
    'UPDATE: Add Size property
    Private m_size As String = "[]"
    Public ReadOnly Property Size() As String
        Get
            If m_size = "[]" Then
                GetSize()
            End If
            Return m_size
        End Get
    End Property
    Private Sub GetSize()
        'Split the file size into bytes, kb, MB and GB
        If (Not Me.IsFolder And Me.IsFileSystem) Or Me.IsDisk Then
            If Me.Length >= (1048576 * 1024) Then
                m_size = Format(Me.Length / (1048576 * 1024), "#,###.# GB")
            ElseIf Me.Length >= 1048576 Then
                m_size = Format(Me.Length / 1048576, "#,###.# MB")
            ElseIf Me.Length >= 1024 Then
                m_size = Format(Me.Length / 1024, "#,### KB")
            ElseIf Not (Me.IsRemovable And Me.Length = 0) Then 'Don't show a CD-ROM's size if it doesn't have a disk in it
                m_size = Format(Me.Length, "##0 Bytes")
            Else
                m_size = "" 'Empty CD-ROM
            End If
        Else
            m_size = ""
        End If
    End Sub

#Region "       Drag Ops Properties"
    Public ReadOnly Property CanMove() As Boolean
        Get
            Return m_CanMove
        End Get
    End Property
    Public ReadOnly Property CanCopy() As Boolean
        Get
            Return m_CanCopy
        End Get
    End Property
    Public ReadOnly Property CanDelete() As Boolean
        Get
            Return m_CanDelete
        End Get
    End Property
    Public ReadOnly Property CanLink() As Boolean
        Get
            Return m_CanLink
        End Get
    End Property
    Public ReadOnly Property IsDropTarget() As Boolean
        Get
            Return m_IsDropTarget
        End Get
    End Property
#End Region

#End Region

#Region "       Filled on Demand Properties"

#Region "           Filled based on m_HasDispType"
    ''' <summary>
    ''' Set DisplayName, TypeName, and SortFlag when actually needed
    ''' </summary>
    ''' 
    Private Sub SetDispType()
        'Get Displayname, TypeName
        Dim shfi As New SHFILEINFO()
        Dim dwflag As Integer = SHGFI.DISPLAYNAME Or _
                                SHGFI.TYPENAME Or _
                                SHGFI.PIDL
        Dim dwAttr As Integer = 0
        If m_IsFileSystem And Not m_IsFolder Then
            dwflag = dwflag Or SHGFI.USEFILEATTRIBUTES
            dwAttr = FILE_ATTRIBUTE_NORMAL
        End If
        Dim H As IntPtr = SHGetFileInfo(m_Pidl, dwAttr, shfi, cbFileInfo, dwflag)
        m_DisplayName = shfi.szDisplayName
        m_TypeName = shfi.szTypeName
        'fix DisplayName
        If m_DisplayName.Equals("") Then
            m_DisplayName = m_Path
        End If
        'Fix TypeName
        'If m_IsFolder And m_TypeName.Equals("File") Then
        '    m_TypeName = "File Folder"
        'End If
        m_SortFlag = ComputeSortFlag()
        m_HasDispType = True
    End Sub

    Public ReadOnly Property DisplayName() As String
        Get
            If Not m_HasDispType Then SetDispType()
            Return m_DisplayName
        End Get
    End Property

    Private ReadOnly Property SortFlag() As Integer
        Get
            If Not m_HasDispType Then SetDispType()
            Return m_SortFlag
        End Get
    End Property

    Public ReadOnly Property TypeName() As String
        Get
            If Not m_HasDispType Then SetDispType()
            Return m_TypeName
        End Get
    End Property
#End Region

#Region "           IconIndex properties"
    Public ReadOnly Property IconIndexNormal() As Integer
        Get
            If m_IconIndexNormal < 0 Then
                If Not m_HasDispType Then SetDispType()
                Dim shfi As New SHFILEINFO()
                Dim dwflag As Integer = SHGFI.PIDL Or _
                                        SHGFI.SYSICONINDEX
                Dim dwAttr As Integer = 0
                If m_IsFileSystem And Not m_IsFolder Then
                    dwflag = dwflag Or SHGFI.USEFILEATTRIBUTES
                    dwAttr = FILE_ATTRIBUTE_NORMAL
                End If
                Dim H As IntPtr = SHGetFileInfo(m_Pidl, dwAttr, shfi, cbFileInfo, dwflag)
                m_IconIndexNormal = shfi.iIcon
            End If
            Return m_IconIndexNormal
        End Get
    End Property
    ' IconIndexOpen is Filled on demand
    Public ReadOnly Property IconIndexOpen() As Integer
        Get
            If m_IconIndexOpen < 0 Then
                If Not m_HasDispType Then SetDispType()
                If Not m_IsDisk And m_IsFileSystem And m_IsFolder Then
                    If OpenFolderIconIndex < 0 Then
                        Dim dwflag As Integer = SHGFI.SYSICONINDEX Or SHGFI.PIDL
                        Dim shfi As New SHFILEINFO()
                        Dim H As IntPtr = SHGetFileInfo(m_Pidl, 0, _
                                          shfi, cbFileInfo, _
                                          dwflag Or SHGFI.OPENICON)
                        m_IconIndexOpen = shfi.iIcon
                        'If m_TypeName.Equals("File Folder") Then
                        '    OpenFolderIconIndex = shfi.iIcon
                        'End If
                    Else
                        m_IconIndexOpen = OpenFolderIconIndex
                    End If
                Else
                    m_IconIndexOpen = m_IconIndexNormal
                End If
            End If
            Return m_IconIndexOpen
        End Get
    End Property
#End Region

#Region "           FileInfo type Information"

    ''' <summary>
    ''' Obtains information available from FileInfo.
    ''' </summary>
    ''' 
    Private Sub FillDemandInfo()
        If m_IsDisk Then
            Try
                'See if this is a network drive
                'NoRoot = 1
                'Removable = 2
                'LocalDisk = 3
                'Network = 4
                'CD = 5
                'RAMDrive = 6
                Dim disk As New Management.ManagementObject("win32_logicaldisk.deviceid=""" & _
                                                m_Path.Substring(0, 2) & """")
                m_Length = CType(disk("Size"), Long)
                If CType(disk("DriveType"), UInt32).ToString = CStr(4) Then
                    m_IsNetWorkDrive = True
                End If
            Catch ex As Exception
                'Disconnected Network Drives etc. will generate 
                'an error here, just assume that it is a network
                'drive
                m_IsNetWorkDrive = True
            Finally
                m_XtrInfo = True
            End Try
        ElseIf Not m_IsDisk And m_IsFileSystem And Not m_IsFolder Then
            'in this case, it's a file
            If File.Exists(m_Path) Then
                Dim fi As New FileInfo(m_Path)
                m_LastWriteTime = fi.LastWriteTime
                m_LastAccessTime = fi.LastAccessTime
                m_CreationTime = fi.CreationTime
                m_Length = fi.Length
                m_XtrInfo = True
            End If
        Else
            If m_IsFileSystem And m_IsFolder Then
                If Directory.Exists(m_Path) Then
                    Dim di As New DirectoryInfo(m_Path)
                    m_LastWriteTime = di.LastWriteTime
                    m_LastAccessTime = di.LastAccessTime
                    m_CreationTime = di.CreationTime
                    m_XtrInfo = True
                End If
            End If
        End If
    End Sub

    Public ReadOnly Property LastWriteTime() As DateTime
        Get
            If Not m_XtrInfo Then
                FillDemandInfo()
            End If
            Return m_LastWriteTime
        End Get
    End Property
    Public ReadOnly Property LastAccessTime() As DateTime
        Get
            If Not m_XtrInfo Then
                FillDemandInfo()
            End If
            Return m_LastAccessTime
        End Get
    End Property
    Public ReadOnly Property CreationTime() As DateTime
        Get
            If Not m_XtrInfo Then
                FillDemandInfo()
            End If
            Return m_CreationTime
        End Get
    End Property
    Public ReadOnly Property Length() As Long
        Get
            If Not m_XtrInfo Then
                FillDemandInfo()
            End If
            Return m_Length
        End Get
    End Property
    Public ReadOnly Property IsNetworkDrive() As Boolean
        Get
            If Not m_XtrInfo Then
                FillDemandInfo()
            End If
            Return m_IsNetWorkDrive
        End Get
    End Property
#End Region

#Region "           cPidl information"
    Public ReadOnly Property clsPidl() As cPidl
        Get
            If IsNothing(m_cPidl) Then
                m_cPidl = New cPidl(m_Pidl)
            End If
            Return m_cPidl
        End Get
    End Property
#End Region

#Region "       IsReadOnly and IsSystem"
    '''<Summary>The IsReadOnly attribute causes an annoying access to any floppy drives
    ''' on the system. To postpone this (or avoid, depending on user action),
    ''' the attribute is only queried when asked for</Summary>
    Public ReadOnly Property IsReadOnly() As Boolean
        Get
            If m_IsReadOnlySetup Then
                Return m_IsReadOnly
            Else
                Dim shfi As New SHFILEINFO()
                shfi.dwAttributes = SFGAO.RDONLY
                Dim dwflag As Integer = SHGFI.PIDL Or _
                                        SHGFI.ATTRIBUTES Or _
                                        SHGFI.ATTR_SPECIFIED
                Dim dwAttr As Integer = 0
                Dim H As IntPtr = SHGetFileInfo(m_Pidl, dwAttr, shfi, cbFileInfo, dwflag)
                If H.ToInt32 <> NOERROR AndAlso H.ToInt32 <> 1 Then
                    Marshal.ThrowExceptionForHR(H.ToInt32)
                End If
                m_IsReadOnly = CBool(shfi.dwAttributes And SFGAO.RDONLY)
                'If m_IsReadOnly Then Debug.WriteLine("IsReadOnly -- " & m_Path)
                m_IsReadOnlySetup = True
                Return m_IsReadOnly
            End If
            'If Not m_XtrInfo Then
            '    FillDemandInfo()
            'End If
            'Return m_Attributes And FileAttributes.ReadOnly = FileAttributes.ReadOnly
        End Get
    End Property
    '''<Summary>The IsSystem attribute is seldom used, but required by DragDrop operations.
    ''' Since there is no way of getting ONLY the System attribute without getting
    ''' the RO attribute (which forces a reference to the floppy drive), we pay
    ''' the price of getting its own File/DirectoryInfo for this purpose alone.
    '''</Summary>
    Public ReadOnly Property IsSystem() As Boolean
        Get
            Static HaveSysInfo As Boolean   'true once we have gotten this attr
            Static m_IsSystem As Boolean    'the value of this attr once we have it
            If Not HaveSysInfo Then
                Try
                    m_IsSystem = (File.GetAttributes(m_Path) And FileAttributes.System) = FileAttributes.System
                    HaveSysInfo = True
                Catch ex As Exception
                    HaveSysInfo = True
                End Try
            End If
            Debug.WriteLine("In IsSystem -- Path = " & m_Path & " IsSystem = " & m_IsSystem)
            Return m_IsSystem
        End Get
    End Property

#End Region

#End Region

#End Region

#Region "   Public Methods"

#Region "       Shared Public Methods"

#Region "       GetDeskTop"
    ''' <summary>
    ''' If not initialized, then build DesktopBase
    ''' once done, or if initialized already,
    ''' </summary>
    '''<returns>The DesktopBase CShItem representing the desktop</returns>
    ''' 
    Public Shared Function GetDeskTop() As CShItem
        If IsNothing(DesktopBase) Then
            DesktopBase = New CShItem()
        End If
        Return DesktopBase
    End Function
#End Region

#Region "       IsAncestorOf"
    '''<Summary>IsAncestorOf returns True if CShItem ancestor is an ancestor of CShItem current
    ''' if OS is Win2K or above, uses the ILIsParent API, otherwise uses the
    ''' cPidl function StartsWith.  This is necessary since ILIsParent in only available
    ''' in Win2K or above systems AND StartsWith fails on some folders on XP systems (most
    ''' obviously some Network Folder Shortcuts, but also Control Panel. Note, StartsWith
    ''' always works on systems prior to XP.
    ''' NOTE: if ancestor and current reference the same Item, both
    ''' methods return True</Summary>
    Public Shared Function IsAncestorOf(ByVal ancestor As CShItem, _
                                        ByVal current As CShItem, _
                                        Optional ByVal fParent As Boolean = False) _
                                        As Boolean
        Return IsAncestorOf(ancestor.PIDL, current.PIDL, fParent)
    End Function
    '''<Summary> Compares a candidate Ancestor PIDL with a Child PIDL and
    ''' returns True if Ancestor is an ancestor of the child.
    ''' if fParent is True, then only return True if Ancestor is the immediate
    ''' parent of the Child</Summary>
    Public Shared Function IsAncestorOf(ByVal AncestorPidl As IntPtr, _
                                        ByVal ChildPidl As IntPtr, _
                                        Optional ByVal fParent As Boolean = False) _
                                        As Boolean
        If Is2KOrAbove() Then
            Return ILIsParent(AncestorPidl, ChildPidl, fParent)
        Else
            Dim Child As New cPidl(ChildPidl)
            Dim Ancestor As New cPidl(AncestorPidl)
            IsAncestorOf = Child.StartsWith(Ancestor)
            If Not IsAncestorOf Then Exit Function
            If fParent Then ' check for immediate ancestor, if desired
                Dim oAncBytes() As Object = Ancestor.Decompose
                Dim oChildBytes() As Object = Child.Decompose
                If oAncBytes.Length <> (oChildBytes.Length - 1) Then
                    IsAncestorOf = False
                End If
            End If
        End If
    End Function
#End Region

#Region "      AllFolderWalk"
    '''<Summary>The WalkAllCallBack delegate defines the signature of 
    ''' the routine to be passed to DirWalker
    ''' Usage:  dim d1 as new CshItem.WalkAllCallBack(addressof yourroutine)
    '''   Callback function receives a CShItem for each file and Directory in
    '''   Starting Directory and each sub-directory of this directory and
    '''   each sub-dir of each sub-dir ....
    '''</Summary>
    Public Delegate Function WalkAllCallBack(ByVal info As CShItem, _
                                             ByVal UserLevel As Integer, _
                                             ByVal Tag As Integer) _
                                             As Boolean
    '''<Summary>
    ''' AllFolderWalk recursively walks down directories from cStart, calling its
    '''   callback routine, WalkAllCallBack, for each Directory and File encountered, including those in
    '''   cStart.  UserLevel is incremented by 1 for each level of dirs that DirWalker
    '''  recurses thru.  Tag in an Integer that is simply passed, unmodified to the 
    '''  callback, with each CShItem encountered, both File and Directory CShItems.
    ''' </Summary>
    ''' <param name="cStart"></param>
    ''' <param name="cback"></param>
    ''' <param name="UserLevel"></param>
    ''' <param name="Tag"></param>
    ''' 
    Public Shared Function AllFolderWalk(ByVal cStart As CShItem, _
                                          ByVal cback As WalkAllCallBack, _
                                          ByVal UserLevel As Integer, _
                                          ByVal Tag As Integer) _
                                          As Boolean
        If Not IsNothing(cStart) AndAlso cStart.IsFolder Then
            Dim cItem As CShItem
            'first processes all files in this directory
            For Each cItem In cStart.GetFiles
                If Not cback(cItem, UserLevel, Tag) Then
                    Return False        'user said stop
                End If
            Next
            'then process all dirs in this directory, recursively
            For Each cItem In cStart.GetDirectories
                If Not cback(cItem, UserLevel + 1, Tag) Then
                    Return False        'user said stop
                Else
                    If Not AllFolderWalk(cItem, cback, UserLevel + 1, Tag) Then
                        Return False
                    End If
                End If
            Next
            Return True
        Else        'Invalid call
            Throw New ApplicationException("AllFolderWalk -- Invalid Start Directory")
        End If
    End Function
#End Region

#End Region

#Region "       Public Instance Methods"

#Region "           Equals"
    Public Overloads Function Equals(ByVal other As CShItem) As Boolean
        Equals = Me.Path.Equals(other.Path)
    End Function
#End Region

#Region "       GetDirectories"
    ''' <summary>
    ''' Returns the Directories of this sub-folder as an ArrayList of CShitems
    ''' </summary>
    ''' <param name="doRefresh">Optional, default=True, Refresh the directories</param>
    ''' <returns>An ArrayList of CShItems. May return an empty ArrayList if there are none.</returns>
    ''' <remarks>revised to alway return an up-to-date list unless 
    ''' specifically instructed not to (useful in constructs like:
    ''' if CSI.RefreshDirectories then
    '''     Dirs = CSI.GetDirectories(False)  ' just did a Refresh </remarks>
    Public Function GetDirectories(Optional ByVal doRefresh As Boolean = True) As ArrayList
        If m_IsFolder Then
            If doRefresh Then
                RefreshDirectories()   ' return an up-to-date List
            ElseIf m_Directories Is Nothing Then
                RefreshDirectories()
            End If
            Return m_Directories
        Else 'if it is not a Folder, then return empty arraylist
            Return New ArrayList()
        End If
    End Function

#End Region

#Region "       GetFiles"
    ''' <summary>
    ''' Returns the Files of this sub-folder as an
    '''   ArrayList of CShitems
    ''' Note: we do not keep the arraylist of files, Generate it each time
    ''' </summary>
    ''' <returns>An ArrayList of CShItems. May return an empty ArrayList if there are none.</returns>
    ''' 
    Public Function GetFiles() As ArrayList
        If m_IsFolder Then
            Return GetContents(SHCONTF.NONFOLDERS Or SHCONTF.INCLUDEHIDDEN)
        Else
            Return New ArrayList()
        End If
    End Function

    ''' <summary>
    ''' Returns the Files of this sub-folder, filtered by a filtering string, as an
    '''   ArrayList of CShitems
    ''' Note: we do not keep the arraylist of files, Generate it each time
    ''' </summary>
    ''' <param name="Filter">A filter string (for example: *.Doc)</param>
    ''' <returns>An ArrayList of CShItems. May return an empty ArrayList if there are none.</returns>
    ''' 
    Public Function GetFiles(ByVal Filter As String) As ArrayList
        If m_IsFolder Then
            Dim dummy As New ArrayList()
            Dim fileentries() As String
            fileentries = Directory.GetFiles(m_Path, Filter)
            Dim vFile As String
            For Each vFile In fileentries
                dummy.Add(New CShItem(vFile))
            Next
            Return dummy
        Else
            Return New ArrayList()
        End If
    End Function
#End Region

#Region "       GetItems"
    ''' <summary>
    ''' Returns the Directories and Files of this sub-folder as an
    '''   ArrayList of CShitems
    ''' Note: we do not keep the arraylist of files, Generate it each time
    ''' </summary>
    ''' <returns>An ArrayList of CShItems. May return an empty ArrayList if there are none.</returns>
    Public Function GetItems() As ArrayList
        Dim rVal As New ArrayList()
        If m_IsFolder Then
            rVal.AddRange(Me.GetDirectories)
            rVal.AddRange(Me.GetContents(SHCONTF.NONFOLDERS Or SHCONTF.INCLUDEHIDDEN))
            rVal.Sort()
            Return rVal
        Else
            Return rVal
        End If
    End Function
#End Region

#Region "       GetFileName"
    '''<Summary>GetFileName returns the Full file name of this item.
    '''  Specifically, for a link file (xxx.txt.lnk for example) the
    '''  DisplayName property will return xxx.txt, this method will
    '''  return xxx.txt.lnk.  In most cases this is equivalent of
    '''  System.IO.Path.GetFileName(m_Path).  However, some m_Paths
    '''  actually are GUIDs.  In that case, this routine returns the
    '''  DisplayName</Summary>
    Public Function GetFileName() As String
        If m_Path.StartsWith("::{") Then 'Path is really a GUID
            Return Me.DisplayName
        Else
            If m_IsDisk Then
                Return m_Path.Substring(0, 1)
            Else
                Return IO.Path.GetFileName(m_Path)
            End If
        End If
    End Function
#End Region

#Region "       ReFreshDirectories"
    '''<Summary> A lower cost way of Refreshing the Directories of this CShItem</Summary>
    '''<returns> Returns True if there were any changes</returns>
    Public Function RefreshDirectories() As Boolean
        RefreshDirectories = False      'value unless there were changes
        If m_IsFolder Then          'if not a folder, then return false
            Dim InvalidDirs As New ArrayList()  'holds CShItems of not found dirs
            If IsNothing(m_Directories) Then
                m_Directories = GetContents(SHCONTF.FOLDERS Or SHCONTF.INCLUDEHIDDEN)
                RefreshDirectories = True     'changed from unexamined to examined
            Else
                'Get relative PIDLs from current directory items
                Dim curPidls As ArrayList = GetContents(SHCONTF.FOLDERS Or SHCONTF.INCLUDEHIDDEN, True)
                Dim iptr As IntPtr      'used below
                If curPidls.Count < 1 Then
                    If m_Directories.Count > 0 Then
                        m_Directories = New ArrayList() 'nothing there anymore
                        RefreshDirectories = True    'Changed from had some to have none
                    Else    'Empty before, Empty now, do nothing -- just a logic marker
                    End If
                Else    'still has some. Are they the same?
                    If m_Directories.Count < 1 Then 'didn't have any before, so different
                        m_Directories = GetContents(SHCONTF.FOLDERS Or SHCONTF.INCLUDEHIDDEN)
                        RefreshDirectories = True     'changed from had none to have some
                    Else    'some before, some now. Same?  This is the complicated part
                        'Firstly, build ArrayLists of Relative Pidls
                        Dim compList As New ArrayList(curPidls)
                        'Since we are only comparing relative PIDLs, build a list of 
                        ' the relative PIDLs of the old content -- saving repeated building
                        Dim iOld As Integer
                        Dim OldRel(m_Directories.Count - 1) As IntPtr
                        For iOld = 0 To m_Directories.Count - 1
                            'GetLastID returns a ptr into an EXISTING IDLIST -- never release that ptr
                            ' and never release the EXISTING IDLIST before thru with OldRel
                            OldRel(iOld) = GetLastID(CType(m_Directories(iOld), CShItem).PIDL)
                        Next
                        Dim iNew As Integer
                        For iOld = 0 To m_Directories.Count - 1
                            For iNew = 0 To compList.Count - 1
                                If IsEqual(CType(compList(iNew), IntPtr), OldRel(iOld)) Then
                                    compList.RemoveAt(iNew)  'Match, don't look at this one again
                                    GoTo NXTOLD    'content item exists in both
                                End If
                            Next
                            'falling thru here means couldn't find iOld entry
                            InvalidDirs.Add(m_Directories(iOld)) 'save off the unmatched CShItem
                            RefreshDirectories = True
NXTOLD:                 Next
                        'any not found should be removed from m_Directories
                        Dim csi As CShItem
                        For Each csi In InvalidDirs
                            m_Directories.Remove(csi)
                        Next
                        'anything remaining in compList is a new entry
                        If compList.Count > 0 Then
                            RefreshDirectories = True
                            For Each iptr In compList   'these are relative PIDLs
                                m_Directories.Add(New CShItem(m_Folder, iptr, Me))
                            Next
                        End If
                        If RefreshDirectories Then 'something added or removed, resort
                            m_Directories.Sort()
                        End If
                    End If
                    'we obtained some new relative PIDLs in curPidls, so free them
                    For Each iptr In curPidls
                        Marshal.FreeCoTaskMem(iptr)
                    Next
                End If  'end of content comparison
            End If      'end of IsNothing test
        End If          'end of IsFolder test
    End Function

#End Region

#Region "       ToString"
    ''' <summary>
    ''' Returns the DisplayName as the normal ToString value
    ''' </summary>
    ''' 
    Public Overrides Function ToString() As String
        Return m_DisplayName
    End Function
#End Region

#Region "       Debug Dumper"
    ''' <summary>
    ''' Summary of DebugDump.
    ''' </summary>
    ''' 
    Public Sub DebugDump()
        Debug.WriteLine("DisplayName = " & m_DisplayName)
        Debug.WriteLine("PIDL        = " & m_Pidl.ToString)
        Debug.WriteLine(vbTab & "Path        = " & m_Path)
        Debug.WriteLine(vbTab & "TypeName    = " & Me.TypeName)
        Debug.WriteLine(vbTab & "iIconNormal = " & m_IconIndexNormal)
        Debug.WriteLine(vbTab & "iIconSelect = " & m_IconIndexOpen)
        Debug.WriteLine(vbTab & "IsBrowsable = " & m_IsBrowsable)
        Debug.WriteLine(vbTab & "IsFileSystem= " & m_IsFileSystem)
        Debug.WriteLine(vbTab & "IsFolder    = " & m_IsFolder)
        Debug.WriteLine(vbTab & "IsLink    = " & m_IsLink)
        Debug.WriteLine(vbTab & "IsDropTarget = " & m_IsDropTarget)
        Debug.WriteLine(vbTab & "IsReadOnly   = " & Me.IsReadOnly)
        Debug.WriteLine(vbTab & "CanCopy = " & Me.CanCopy)
        Debug.WriteLine(vbTab & "CanLink = " & Me.CanLink)
        Debug.WriteLine(vbTab & "CanMove = " & Me.CanMove)
        Debug.WriteLine(vbTab & "CanDelete = " & Me.CanDelete)
        If m_IsFolder Then
            If Not IsNothing(m_Directories) Then
                Debug.WriteLine(vbTab & "Directory Count = " & m_Directories.Count)
            Else
                Debug.WriteLine(vbTab & "Directory Count Not yet set")
            End If
        End If
    End Sub
#End Region

#Region "       GetDropTargetOf"
    Public Function GetDropTargetOf(ByVal tn As Control) As IDropTarget
        If IsNothing(m_Folder) Then Return Nothing
        Dim apidl(0) As IntPtr
        Dim HR As Integer
        Dim theInterface As IDropTarget = Nothing
        Dim tnH As IntPtr = tn.Handle
        HR = m_Folder.CreateViewObject(tnH, ShellDll.IID_IDropTarget, theInterface)
        If HR <> 0 Then
            Marshal.ThrowExceptionForHR(HR)
        End If
        Return theInterface
    End Function
#End Region

#End Region

#End Region

#Region "   Private Instance Methods"

#Region "       GetContents"
    '''<Summary>
    ''' Returns the requested Items of this Folder as an ArrayList of CShitems
    '''  unless the IntPtrOnly flag is set.  If IntPtrOnly is True, return an
    '''  ArrayList of PIDLs.
    '''</Summary>
    ''' <param name="flags">A set of one or more SHCONTF flags indicating which items to return</param>
    ''' <param name="IntPtrOnly">True to suppress generation of CShItems, returning only an
    '''  ArrayList of IntPtrs to RELATIVE PIDLs for the contents of this Folder</param>
    Private Function GetContents(ByVal flags As SHCONTF, Optional ByVal IntPtrOnly As Boolean = False) As ArrayList
        Dim rVal As New ArrayList()
        Dim HR As Integer
        Dim IEnum As IEnumIDList = Nothing
        HR = m_Folder.EnumObjects(0, flags, IEnum)
        If HR = NOERROR Then
            Dim item As IntPtr = IntPtr.Zero
            Dim itemCnt As Integer
            HR = IEnum.GetNext(1, item, itemCnt)
            If HR = NOERROR Then
                Do While itemCnt > 0 AndAlso Not item.Equals(IntPtr.Zero)
                    'there is no setting to exclude folders from enumeration,
                    ' just one to include non-folders
                    ' so we have to screen the results to return only
                    '  non-folders if folders are not wanted
                    If Not CBool(flags And SHCONTF.FOLDERS) Then 'don't want folders. see if this is one
                        Dim attrFlag As SFGAO
                        attrFlag = attrFlag Or SFGAO.FOLDER Or _
                                                SFGAO.STREAM
                        'Note: for GetAttributesOf, we must provide an array, in all cases with 1 element
                        Dim aPidl(0) As IntPtr
                        aPidl(0) = item
                        m_Folder.GetAttributesOf(1, aPidl, attrFlag)
                        If Not XPorAbove Then
                            If CBool(attrFlag And SFGAO.FOLDER) Then 'Don't need it
                                GoTo SKIPONE
                            End If
                        Else         'XP or above
                            If CBool(attrFlag And SFGAO.FOLDER) AndAlso _
                               Not CBool(attrFlag And SFGAO.STREAM) Then
                                GoTo SKIPONE
                            End If
                        End If
                    End If
                    If IntPtrOnly Then   'just relative pidls for fast look, no CShITem overhead
                        rVal.Add(PIDLClone(item))   'caller must free
                    Else
                        rVal.Add(New CShItem(m_Folder, item, Me))
                    End If
SKIPONE:            Marshal.FreeCoTaskMem(item) 'if New kept it, it kept a copy
                    item = IntPtr.Zero
                    itemCnt = 0
                    ' Application.DoEvents()
                    HR = IEnum.GetNext(1, item, itemCnt)
                Loop
            Else
                If HR <> 1 Then GoTo HRError '1 means no more
            End If
        Else : GoTo HRError
        End If
        'Normal Exit
NORMAL: If Not IsNothing(IEnum) Then
            Marshal.ReleaseComObject(IEnum)
        End If
        rVal.TrimToSize()
        Return rVal

        ' Error Exit for all Com errors
HRError:  'not ready disks will return the following error
        'If HR = &HFFFFFFFF800704C7 Then
        '    GoTo NORMAL
        'ElseIf HR = &HFFFFFFFF80070015 Then
        '    GoTo NORMAL
        '    'unavailable net resources will return these
        'ElseIf HR = &HFFFFFFFF80040E96 Or HR = &HFFFFFFFF80040E19 Then
        '    GoTo NORMAL
        'ElseIf HR = &HFFFFFFFF80004001 Then 'Certain "Not Implemented" features will return this
        '    GoTo NORMAL
        'ElseIf HR = &HFFFFFFFF80004005 Then
        '    GoTo NORMAL
        'ElseIf HR = &HFFFFFFFF800704C6 Then
        '    GoTo NORMAL
        'End If
        If Not IsNothing(IEnum) Then Marshal.ReleaseComObject(IEnum)
        '#If Debug Then
        '        Marshal.ThrowExceptionForHR(HR)
        '#End If
        rVal = New ArrayList() 'sometimes it is a non-fatal error,ignored
        GoTo NORMAL
    End Function
#End Region

#Region "       Really nasty Pidl manipulation"

    ''' <Summary>
    ''' Get Size in bytes of the first (possibly only)
    '''  SHItem in an ID list.  Note: the full size of
    '''   the item is the sum of the sizes of all SHItems
    '''   in the list!!
    ''' </Summary>
    ''' <param name="pidl"></param>
    ''' 
    Private Shared Function ItemIDSize(ByVal pidl As IntPtr) As Integer
        If Not pidl.Equals(IntPtr.Zero) Then
            Dim b(1) As Byte
            Marshal.Copy(pidl, b, 0, 2)
            Return b(1) * 256 + b(0)
        Else
            Return 0
        End If
    End Function

    ''' <summary>
    ''' computes the actual size of the ItemIDList pointed to by pidl
    ''' </summary>
    ''' <param name="pidl">The pidl pointing to an ItemIDList</param>
    '''<returns> Returns actual size of the ItemIDList, less the terminating nulnul</returns> 
    Public Shared Function ItemIDListSize(ByVal pidl As IntPtr) As Integer
        If Not pidl.Equals(IntPtr.Zero) Then
            Dim i As Integer = ItemIDSize(pidl)
            Dim b As Integer = Marshal.ReadByte(pidl, i) + (Marshal.ReadByte(pidl, i + 1) * 256)
            Do While b > 0
                i += b
                b = Marshal.ReadByte(pidl, i) + (Marshal.ReadByte(pidl, i + 1) * 256)
            Loop
            Return i
        Else : Return 0
        End If
    End Function
    ''' <summary>
    ''' Counts the total number of SHItems in input pidl
    ''' </summary>
    ''' <param name="pidl">The pidl to obtain the count for</param>
    ''' <returns> Returns the count of SHItems pointed to by pidl</returns> 
    Public Shared Function PidlCount(ByVal pidl As IntPtr) As Integer
        If Not pidl.Equals(IntPtr.Zero) Then
            Dim cnt As Integer = 0
            Dim i As Integer = 0
            Dim b As Integer = Marshal.ReadByte(pidl, i) + (Marshal.ReadByte(pidl, i + 1) * 256)
            Do While b > 0
                cnt += 1
                i += b
                b = Marshal.ReadByte(pidl, i) + (Marshal.ReadByte(pidl, i + 1) * 256)
            Loop
            Return cnt
        Else : Return 0
        End If
    End Function

    '''<Summary>GetLastId -- returns a pointer to the last ITEMID in a valid
    ''' ITEMIDLIST. Returned pointer SHOULD NOT be released since it
    ''' points to place within the original PIDL</Summary>
    '''<returns>IntPtr pointing to last ITEMID in ITEMIDLIST structure,
    ''' Returns IntPtr.Zero if given a null pointer.
    ''' If given a pointer to the Desktop, will return same pointer.</returns>
    '''<remarks>This is what the API ILFindLastID does, however IL... 
    ''' functions are not supported before Win2K.</remarks>
    Public Shared Function GetLastID(ByVal pidl As IntPtr) As IntPtr
        If Not pidl.Equals(IntPtr.Zero) Then
            Dim prev As Integer = 0
            Dim i As Integer = 0
            Dim b As Integer = Marshal.ReadByte(pidl, i) + (Marshal.ReadByte(pidl, i + 1) * 256)
            Do While b > 0
                prev = i
                i += b
                b = Marshal.ReadByte(pidl, i) + (Marshal.ReadByte(pidl, i + 1) * 256)
            Loop
            Return New IntPtr(pidl.ToInt32 + prev)
        Else : Return IntPtr.Zero
        End If
    End Function

    Public Shared Function DecomposePIDL(ByVal pidl As IntPtr) As IntPtr()
        Dim lim As Integer = ItemIDListSize(pidl)
        Dim PIDLs(PidlCount(pidl) - 1) As IntPtr
        Dim i As Integer = 0
        Dim curB As Integer
        Dim offSet As Integer = 0
        Do While curB < lim
            Dim thisPtr As IntPtr = New IntPtr(pidl.ToInt32 + curB)
            offSet = Marshal.ReadByte(thisPtr) + (Marshal.ReadByte(thisPtr, 1) * 256)
            PIDLs(i) = Marshal.AllocCoTaskMem(offSet + 2)
            Dim b(offSet + 1) As Byte
            Marshal.Copy(thisPtr, b, 0, offSet)
            b(offSet) = 0 : b(offSet + 1) = 0
            Marshal.Copy(b, 0, PIDLs(i), offSet + 2)
            'DumpPidl(PIDLs(i))
            curB += offSet
            i += 1
        Loop
        Return PIDLs
    End Function

    Private Shared Function PIDLClone(ByVal pidl As IntPtr) As IntPtr
        Dim cb As Integer = ItemIDListSize(pidl)
        Dim b(cb + 1) As Byte
        Marshal.Copy(pidl, b, 0, cb) 'not including terminating nulnul
        b(cb) = 0 : b(cb + 1) = 0 'force to nulnul
        PIDLClone = Marshal.AllocCoTaskMem(cb + 2)
        Marshal.Copy(b, 0, PIDLClone, cb + 2)
    End Function

    Public Shared Function IsEqual(ByVal Pidl1 As IntPtr, ByVal Pidl2 As IntPtr) As Boolean
        If Win2KOrAbove Then
            Return ILIsEqual(Pidl1, Pidl2)
        Else 'do hard way, may not work for some folders on XP

            Dim cb1 As Integer, cb2 As Integer
            cb1 = ItemIDListSize(Pidl1)
            cb2 = ItemIDListSize(Pidl2)
            If cb1 <> cb2 Then Return False
            Dim lim32 As Integer = cb1 \ 4

            Dim i As Integer
            For i = 0 To lim32 - 1
                If Marshal.ReadInt32(Pidl1, i) <> Marshal.ReadInt32(Pidl2, i) Then
                    Return False
                End If
            Next
            Dim limB As Integer = cb1 Mod 4
            Dim offset As Integer = lim32 * 4
            For i = 0 To limB - 1
                If Marshal.ReadByte(Pidl1, offset + i) <> Marshal.ReadByte(Pidl2, offset + i) Then
                    Return False
                End If
            Next
            Return True 'made it to here, so they are equal
        End If
    End Function

    ''' <summary>
    ''' Concatenates the contents of two pidls into a new Pidl (ended by 00)
    ''' allocating CoTaskMem to hold the result,
    ''' placing the concatenation (followed by 00) into the
    ''' allocated Memory,
    ''' and returning an IntPtr pointing to the allocated mem
    ''' </summary>
    ''' <param name="pidl1">IntPtr to a well formed SHItemIDList or IntPtr.Zero</param>
    ''' <param name="pidl2">IntPtr to a well formed SHItemIDList or IntPtr.Zero</param>
    ''' <returns>Returns a ptr to an ItemIDList containing the 
    '''   concatenation of the two (followed by the req 2 zeros
    '''   Caller must Free this pidl when done with it</returns> 
    Public Shared Function concatPidls(ByVal pidl1 As IntPtr, ByVal pidl2 As IntPtr) As IntPtr
        Dim cb1 As Integer, cb2 As Integer
        cb1 = ItemIDListSize(pidl1)
        cb2 = ItemIDListSize(pidl2)
        Dim rawCnt As Integer = cb1 + cb2
        If (rawCnt) > 0 Then
            Dim b(rawCnt + 1) As Byte
            If cb1 > 0 Then
                Marshal.Copy(pidl1, b, 0, cb1)
            End If
            If cb2 > 0 Then
                Marshal.Copy(pidl2, b, cb1, cb2)
            End If
            Dim rVal As IntPtr = Marshal.AllocCoTaskMem(cb1 + cb2 + 2)
            b(rawCnt) = 0 : b(rawCnt + 1) = 0
            Marshal.Copy(b, 0, rVal, rawCnt + 2)
            Return rVal
        Else
            Return IntPtr.Zero
        End If
    End Function

    ''' <summary>
    ''' Returns an ItemIDList with the last ItemID trimed off
    '''  This is necessary since I cannot get SHBindToParent to work 
    '''  It's purpose is to generate an ItemIDList for the Parent of a
    '''  Special Folder which can then be processed with DesktopBase.BindToObject,
    '''  yeilding a Folder for the parent of the Special Folder
    '''  It also creates and passes back a RELATIVE pidl for this item
    ''' </summary>
    ''' <param name="pidl">A pointer to a well formed ItemIDList. The PIDL to trim</param>
    ''' <param name="relPidl">BYREF IntPtr which will point to a new relative pidl
    '''        containing the contents of the last ItemID in the ItemIDList
    '''        terminated by the required 2 nulls.</param>
    ''' <returns> an ItemIDList with the last element removed.
    '''  Caller must Free this item when through with it
    '''  Also returns the new relative pidl in the 2nd parameter
    '''   Caller must Free this pidl as well, when through with it
    '''</returns>
    Public Shared Function TrimPidl(ByVal pidl As IntPtr, ByRef relPidl As IntPtr) As IntPtr
        Dim cb As Integer = ItemIDListSize(pidl)
        Dim b(cb + 1) As Byte
        Marshal.Copy(pidl, b, 0, cb)
        Dim prev As Integer = 0
        Dim i As Integer = b(0) + (b(1) * 256)
        'Do While i < cb AndAlso b(i) <> 0
        Do While i > 0 AndAlso i < cb       'Changed code
            prev = i
            i += b(i) + (b(i + 1) * 256)
        Loop
        If (prev + 1) < cb Then
            'first set up the relative pidl
            b(cb) = 0
            b(cb + 1) = 0
            Dim cb1 As Integer = b(prev) + (b(prev + 1) * 256)
            relPidl = Marshal.AllocCoTaskMem(cb1 + 2)
            Marshal.Copy(b, prev, relPidl, cb1 + 2)
            b(prev) = 0 : b(prev + 1) = 0
            Dim rVal As IntPtr = Marshal.AllocCoTaskMem(prev + 2)
            Marshal.Copy(b, 0, rVal, prev + 2)
            Return rVal
        Else
            Return IntPtr.Zero
        End If
    End Function

#Region "   DumpPidl Routines"
    ''' <summary>
    ''' Dumps, to the Debug output, the contents of the mem block pointed to by
    ''' a PIDL. Depends on the internal structure of a PIDL
    ''' </summary>
    ''' <param name="pidl">The IntPtr(a PIDL) pointing to the block to dump</param>
    ''' 
    Public Shared Sub DumpPidl(ByVal pidl As IntPtr)
        Dim cb As Integer = ItemIDListSize(pidl)
        Debug.WriteLine("PIDL " & pidl.ToString & " contains " & cb & " bytes")
        If cb > 0 Then
            Dim b(cb + 1) As Byte
            Marshal.Copy(pidl, b, 0, cb + 1)
            Dim pidlCnt As Integer = 1
            Dim i As Integer = b(0) + (b(1) * 256)
            Dim curB As Integer = 0
            Do While i > 0
                Debug.Write("ItemID #" & pidlCnt & " Length = " & i)
                DumpHex(b, curB, curB + i - 1)
                pidlCnt += 1
                curB += i
                i = b(curB) + (b(curB + 1) * 256)
            Loop
        End If
    End Sub

    '''<Summary>Dump a portion or all of a Byte Array to Debug output</Summary>
    '''<param name = "b">A single dimension Byte Array</param>
    '''<param name = "sPos">Optional start index of area to dump (default = 0)</param>
    '''<param name = "epos">Optional last index position to dump (default = end of array)</param>
    '''<Remarks>
    '''</Remarks>
    Public Shared Sub DumpHex(ByVal b() As Byte, _
                            Optional ByVal sPos As Integer = 0, _
                            Optional ByVal ePos As Integer = 0)
        If ePos = 0 Then ePos = b.Length - 1
        Dim j As Integer
        Dim curB As Integer = sPos
        Dim sTmp As String
        Dim ch As Char
        Dim SBH As New StringBuilder()
        Dim SBT As New StringBuilder()
        For j = 0 To ePos - sPos
            If j Mod 16 = 0 Then
                Debug.WriteLine(SBH.ToString & SBT.ToString)
                SBH = New StringBuilder() : SBT = New StringBuilder("          ")
                SBH.Append(HexNum(j + sPos, 4) & "). ")
            End If
            If b(curB) < 16 Then
                sTmp = "0" & Hex(b(curB))
            Else
                sTmp = Hex(b(curB))
            End If
            SBH.Append(sTmp) : SBH.Append(" ")
            ch = Chr(b(curB))
            If Char.IsControl(ch) Then
                SBT.Append(".")
            Else
                SBT.Append(ch)
            End If
            curB += 1
        Next

        Dim fill As Integer = (j) Mod 16
        If fill <> 0 Then
            SBH.Append(" "c, 48 - (3 * ((j) Mod 16)))
        End If
        Debug.WriteLine(SBH.ToString & SBT.ToString)
    End Sub

    Public Shared Function HexNum(ByVal num As Integer, ByVal nrChrs As Integer) As String
        Dim h As String = Hex(num)
        Dim SB As New StringBuilder()
        Dim i As Integer
        For i = 1 To nrChrs - h.Length
            SB.Append("0")
        Next
        SB.Append(h)
        Return SB.ToString
    End Function
#End Region

#End Region

#End Region

#Region "   TagComparer Class"
    '''<Summary> It is sometimes useful to sort a list of TreeNodes,
    ''' ListViewItems, or other objects in an order based on CShItems in their Tag
    ''' use this Icomparer for that Sort</Summary>
    Public Class TagComparer
        Implements IComparer
        Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer _
         Implements IComparer.Compare
            Dim xTag As CShItem = x.tag
            Dim yTag As CShItem = y.tag
            Return xTag.CompareTo(y.tag)
        End Function
    End Class
#End Region

#Region "   cPidl Class"
    '''<Summary>cPidl class contains a Byte() representation of a PIDL and
    ''' certain Methods and Properties for comparing one cPidl to another</Summary>
    Public Class cPidl
        Implements IEnumerable

#Region "       Private Fields"
        Dim m_bytes() As Byte   'The local copy of the PIDL
        Dim m_ItemCount As Integer      'the # of ItemIDs in this ItemIDList (PIDL)
        Dim m_OffsetToRelative As Integer 'the index of the start of the last itemID in m_bytes
#End Region

#Region "       Constructor"
        Sub New(ByVal pidl As IntPtr)
            Dim cb As Integer = ItemIDListSize(pidl)
            If cb > 0 Then
                ReDim m_bytes(cb + 1)
                Marshal.Copy(pidl, m_bytes, 0, cb)
                'DumpPidl(pidl)
            Else
                ReDim m_bytes(1)  'This is the DeskTop (we hope)
            End If
            'ensure nulnul
            m_bytes(m_bytes.Length - 2) = 0 : m_bytes(m_bytes.Length - 1) = 0
            m_ItemCount = PidlCount(pidl)
        End Sub
#End Region

#Region "       Public Properties"
        Public ReadOnly Property PidlBytes() As Byte()
            Get
                Return m_bytes
            End Get
        End Property

        Public ReadOnly Property Length() As Integer
            Get
                Return m_bytes.Length
            End Get
        End Property

        Public ReadOnly Property ItemCount() As Integer
            Get
                Return m_ItemCount
            End Get
        End Property

#End Region

#Region "       Public Intstance Methods -- ToPIDL, Decompose, and IsEqual"

        '''<Summary> Copy the contents of a byte() containing a pidl to
        '''  CoTaskMemory, returning an IntPtr that points to that mem block
        ''' Assumes that this cPidl is properly terminated, as all New 
        ''' cPidls are.
        ''' Caller must Free the returned IntPtr when done with mem block.
        '''</Summary>
        Public Function ToPIDL() As IntPtr
            ToPIDL = BytesToPidl(m_bytes)
        End Function

        '''<Summary>Returns an object containing a byte() for each of this cPidl's
        ''' ITEMIDs (individual PIDLS), in order such that obj(0) is
        ''' a byte() containing the bytes of the first ITEMID, etc.
        ''' Each ITEMID is properly terminated with a nulnul
        '''</Summary>
        Public Function Decompose() As Object()
            Dim bArrays(Me.ItemCount - 1) As Object
            Dim eByte As ICPidlEnumerator = CType(Me.GetEnumerator(), ICPidlEnumerator)
            Dim i As Integer
            Do While eByte.MoveNext
                bArrays(i) = eByte.Current
                i += 1
            Loop
            Return bArrays
        End Function

        '''<Summary>Returns True if input cPidl's content exactly match the 
        ''' contents of this instance</Summary>
        Public Function IsEqual(ByVal other As cPidl) As Boolean
            IsEqual = False     'assume not
            If other.Length <> Me.Length Then Exit Function
            Dim ob() As Byte = other.PidlBytes
            Dim i As Integer
            For i = 0 To Me.Length - 1  'note: we look at nulnul also
                If ob(i) <> m_bytes(i) Then Exit Function
            Next
            Return True         'all equal on fall thru
        End Function
#End Region

#Region "       Public Shared Methods"

#Region "           JoinPidlBytes"
        '''<Summary> Join two byte arrays containing PIDLS, returning a 
        ''' Byte() containing the resultant ITEMIDLIST. Both Byte() must
        ''' be properly terminated (nulnul)
        ''' Returns NOTHING if error
        ''' </Summary>
        Public Shared Function JoinPidlBytes(ByVal b1() As Byte, ByVal b2() As Byte) As Byte()
            If IsValidPidl(b1) And IsValidPidl(b2) Then
                Dim b(b1.Length + b2.Length - 3) As Byte 'allow for leaving off first nulnul
                Array.Copy(b1, b, b1.Length - 2)
                Array.Copy(b2, 0, b, b1.Length - 2, b2.Length)
                If IsValidPidl(b) Then
                    Return b
                Else
                    Return Nothing
                End If
            Else
                Return Nothing
            End If
        End Function
#End Region

#Region "           BytesToPidl"
        '''<Summary> Copy the contents of a byte() containing a pidl to
        '''  CoTaskMemory, returning an IntPtr that points to that mem block
        ''' Caller must free the IntPtr when done with it
        '''</Summary>
        Public Shared Function BytesToPidl(ByVal b() As Byte) As IntPtr
            BytesToPidl = IntPtr.Zero       'assume failure
            If IsValidPidl(b) Then
                Dim bLen As Integer = b.Length
                BytesToPidl = Marshal.AllocCoTaskMem(bLen)
                If BytesToPidl.Equals(IntPtr.Zero) Then Exit Function 'another bad error
                Marshal.Copy(b, 0, BytesToPidl, bLen)
            End If
        End Function
#End Region

#Region "           StartsWith"
        '''<Summary>returns True if the beginning of pidlA matches PidlB exactly for pidlB's entire length</Summary>
        Public Shared Function StartsWith(ByVal pidlA As IntPtr, ByVal pidlB As IntPtr) As Boolean
            Return cPidl.StartsWith(New cPidl(pidlA), New cPidl(pidlB))
        End Function

        '''<Summary>returns True if the beginning of A matches B exactly for B's entire length</Summary>
        Public Shared Function StartsWith(ByVal A As cPidl, ByVal B As cPidl) As Boolean
            Return A.StartsWith(B)
        End Function

        '''<Summary>Returns true if the CPidl input parameter exactly matches the
        ''' beginning of this instance of CPidl</Summary>
        Public Function StartsWith(ByVal cp As cPidl) As Boolean
            Dim b() As Byte = cp.PidlBytes
            If b.Length > m_bytes.Length Then Return False 'input is longer
            Dim i As Integer
            For i = 0 To b.Length - 3 'allow for nulnul at end of cp.PidlBytes
                If b(i) <> m_bytes(i) Then Return False
            Next
            Return True
        End Function
#End Region

#End Region

#Region "       GetEnumerator"
        Public Function GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
            Return New ICPidlEnumerator(m_bytes)
        End Function
#End Region

#Region "       PIDL enumerator Class"
        Private Class ICPidlEnumerator
            Implements IEnumerator

            Private m_sPos As Integer   'the first index in the current PIDL
            Private m_ePos As Integer   'the last index in the current PIDL
            Private m_bytes() As Byte   'the local copy of the PIDL
            Private m_NotEmpty As Boolean = False 'the desktop PIDL is zero length

            Sub New(ByVal b() As Byte)
                m_bytes = b
                If b.Length > 0 Then m_NotEmpty = True
                m_sPos = -1 : m_ePos = -1
            End Sub

            Public ReadOnly Property Current() As Object Implements System.Collections.IEnumerator.Current
                Get
                    If m_sPos < 0 Then Throw New InvalidOperationException("ICPidlEnumerator --- attempt to get Current with invalidated list")
                    Dim b((m_ePos - m_sPos) + 2) As Byte    'room for nulnul
                    Array.Copy(m_bytes, m_sPos, b, 0, b.Length - 2)
                    b(b.Length - 2) = 0 : b(b.Length - 1) = 0 'add nulnul
                    Return b
                End Get
            End Property

            Public Function MoveNext() As Boolean Implements System.Collections.IEnumerator.MoveNext
                If m_NotEmpty Then
                    If m_sPos < 0 Then
                        m_sPos = 0 : m_ePos = -1
                    Else
                        m_sPos = m_ePos + 1
                    End If
                    If m_bytes.Length < m_sPos + 1 Then Throw New InvalidCastException("Malformed PIDL")
                    Dim cb As Integer = m_bytes(m_sPos) + m_bytes(m_sPos + 1) * 256
                    If cb = 0 Then
                        Return False 'have passed all back
                    Else
                        m_ePos += cb
                    End If
                Else
                    m_sPos = 0 : m_ePos = 0
                    Return False        'in this case, we have exhausted the list of 0 ITEMIDs
                End If
                Return True
            End Function

            Public Sub Reset() Implements System.Collections.IEnumerator.Reset
                m_sPos = -1 : m_ePos = -1
            End Sub
        End Class
#End Region

    End Class
#End Region

    'UPDATE: Add IsSharePoint property
    Public ReadOnly Property IsSharePoint() As Boolean
        Get
            If m_Path.ToLower.StartsWith("http://") Then
                Return True
            Else
                Return False
            End If
        End Get
    End Property

End Class

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions