Treeview archive contents

Jul 6, 2009 at 8:23 PM

Cant seem to find any examples on this, I can successfully add all the files into the treeview no problem. Its the child nodes im having issues with is there any way to create a working treeview from the archive using this library or a combination of this library and VB.NET objects?

So far i have tried this ...

    Public Sub LoadFolderTree(ByVal path As String)
        Dim basenode As System.Windows.Forms.TreeNode

        Using zip As ZipFile = ZipFile.Read(path)
            Dim e As ZipEntry

            'set main file as master node'
            Dim Index As Integer = path.LastIndexOf("\")
            Dim masterpath As String = path.Substring(Index + 1, path.Length - Index - 1)
            Dim master As TreeNode = MyTreeView.Nodes.Add(masterpath)

            Try
                For Each e In zip
                    With master
                        If e.IsDirectory Then
                            basenode = .Nodes.Add(e.FileName.ToString)
                            basenode.Tag = e.FileName.ToString

                            For Each subE In e.FileName
                                ' need to figure out how to go through each file/folder '
                                ' and append to the respective parent node '
                            Next
                        Else
                            basenode = .Nodes.Add(e.FileName.ToString)
                            basenode.Tag = e.FileName.ToString
                        End If

                        ' auto expand the main node '
                        .Expand()
                    End With
                Next
            Catch excep As Exception
                MsgBox(excep.Message)
            End Try

        End Using
    End Sub

which is activated with this sub ..

    Private Sub findSource_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles findSource.Click
        Dim fdlg As OpenFileDialog = New OpenFileDialog()

        fdlg.Title = "Browse for a file"
        fdlg.InitialDirectory = "c:\"
        fdlg.FilterIndex = 2
        fdlg.RestoreDirectory = True
        If fdlg.ShowDialog() = DialogResult.OK Then
            sourceFilePath.Text = fdlg.FileName
            LoadFolderTree(fdlg.FileName)
        End If
    End Sub
Now i know how to do this type of thing with a regular directory structure, but being a ZIP archive is this even possible? My normal folder structure method fails because it tries to locate the sub folders in the main folder which in the archive do not physically exist to VB.NET at least i don't think. Thanks for any tips that can be uncovered!

 

Coordinator
Jul 6, 2009 at 11:17 PM
Edited Jul 7, 2009 at 2:07 PM

Here's the thing - in a zip archive a "directory" is not really a container per se.   If a zipfile contains "a directory", it is just a zero-length entry that is marked as a directory.  But it is just fine if a directory entry does not exist in the zip archive.  The files in an archive, once extracted, will appear under the corresponding filesystem directory as specified in the filename for that particular entry.  This is what I mean by "a directory isn't really a container."   The only reason to include a directory entry explicitly into a zip archive is in the case where there are no files that would appear in that directory, and still you want to ensure that when unpacked, the filesystem contains an empty directory by that name. Does that make sense?  And the result is that there are typically no directory entries in a zip archive, even if there are pathnames for the entries.

If you want to produce a treeview, you will have to map between the zip file "model' of seeing things, where there are no directories, and the treeview model. 

In your code, you test IsDirectory(), which would make sense if every path segment were represented with a directory, just as with a filesystem.  But that's not always the case in a zip file.  So I think you need to recurse on each path segment for each entry, and test if there is a node in your treeview corresponding to that partial path. If it does not exist, create it.  You may find a zipfile that contains /a/b/c/d/file.txt, and you will have to create 4 nodes in the treeview as you work through the path.  Finally you add the full FileName itself to the most deeply nested directory node.

Here's one way to do it:

    Private Sub PopulateTreeView()
        Try
            zip = ZipFile.Read(Me.TextBox1.Text)

            Me.TreeView1.Nodes.Clear()
            For Each e As ZipEntry In zip
                AddTreeNode(e.FileName)
            Next

        Catch ex As Exception
            MessageBox.Show("Exception: " + ex.ToString(), "Exception during zip processing", _
                            MessageBoxButtons.OK, MessageBoxIcon.Exclamation)

        Finally
            If Not (zip Is Nothing) Then
                zip.Dispose()
            End If

        End Try
    End Sub

    Private Function AddTreeNode(ByVal name As String) As TreeNode
        ' If the entry is a directory, it ends in a slash.  Let's trim it.
        If (name.EndsWith("/")) Then
            name = name.Substring(0, name.Length - 1)
        End If
        Dim node As TreeNode = FindNodeForTag(name, Me.TreeView1.Nodes)
        If Not (node Is Nothing) Then
            Return node
        End If
        ' nodeCollection = where to add the current node.
        Dim nodeCollection As TreeNodeCollection
        Dim parent As String = Path.GetDirectoryName(name)
        If (parent = "") Then
            ' we will add the named node to the root
            nodeCollection = Me.TreeView1.Nodes
        Else
            ' First add a parent node, and then we will 
            ' add the named node as a sub-node of that parent.
            ' NB: must replace backslash with fwd-slash.  The names 
            ' in a zip file use fwd slash.  The paths returned from 
            ' Path.GetDirectoryName use backslash. 
            nodeCollection = AddTreeNode(parent.Replace("\", "/")).Nodes
        End If
        ' the node to be added
        node = New TreeNode
        ' the text that is displayed: just the last part of the path
        node.Text = Path.GetFileName(name)
        ' we use the tag to find nodes, so use the full path here
        node.Tag = name
        ' add it
        nodeCollection.Add(node)
        ' return the node just added 
        Return node
    End Function

    ' return the node with a tag matching the given name
    Private Function FindNodeForTag(ByVal name As String, _
                                    ByRef nodes As TreeNodeCollection) As TreeNode
        For Each node As TreeNode In nodes
            If (name = node.Tag) Then
                Return node
            ElseIf (name.StartsWith(node.Tag + "/")) Then
                Return FindNodeForTag(name, node.Nodes)
            End If
        Next
        Return Nothing
    End Function

This probably isn't the most efficient way to do it. I'm not a WinForms genius and VB is not my thing. But this works.

 

Jul 7, 2009 at 1:44 PM

Great that is exactly what i was trying to do. Executiion time is minimal actually. Little cleanup that VS2008 didnt care for but very little ... if anyone is intrested his example works ... but cleaned up to produce no errors in VS2008 is this ...

    Private Sub PopulateTreeView(ByVal zipSource As String)
        Using zip As ZipFile = ZipFile.Read(zipSource)

            Try
                MyTreeView.Nodes.Clear()

                For Each e As ZipEntry In zip
                    AddTreeNode(e.FileName)
                Next

            Catch ex As Exception
                MessageBox.Show("Exception: " + ex.ToString(), "Exception during zip processing", _
                                MessageBoxButtons.OK, MessageBoxIcon.Exclamation)

            Finally
                If zip Is Nothing Then
                    zip.Dispose()
                End If

            End Try
        End Using
    End Sub

    Private Function AddTreeNode(ByVal name As String) As TreeNode
        ' If the entry is a directory, it ends in a slash.  Let's trim it.
        If name.EndsWith("/") Then
            name = name.Substring(0, name.Length - 1)
        End If

        Dim node As TreeNode = FindNodeForTag(name, MyTreeView.Nodes)
        If Not node Is Nothing Then
            Return node
        End If

        ' nodeCollection = where to add the current node.
        Dim nodeCollection As TreeNodeCollection
        Dim parent As String = Path.GetDirectoryName(name)
        If parent = "" Then
            ' we will add the named node to the root
            nodeCollection = MyTreeView.Nodes
        Else
            ' First add a parent node, and then we will 
            ' add the named node as a sub-node of that parent.
            ' NB: must replace backslash with fwd-slash.  The names 
            ' in a zip file use fwd slash.  The paths returned from 
            ' Path.GetDirectoryName use backslash. 
            nodeCollection = AddTreeNode(parent.Replace("\", "/")).Nodes
        End If

        ' the node to be added
        node = New TreeNode

        ' the text that is displayed: just the last part of the path
        node.Text = Path.GetFileName(name)

        ' we use the tag to find nodes, so use the full path here
        node.Tag = name

        ' add it
        nodeCollection.Add(node)

        ' return the node just added 
        Return node
    End Function

    ' return the node with a tag matching the given name
    Private Function FindNodeForTag(ByVal name As String, _
                                    ByRef nodes As TreeNodeCollection) As TreeNode
        For Each node As TreeNode In nodes
            If name = node.Tag Then
                Return node
            ElseIf name.StartsWith(node.Tag + "/") Then
                Return FindNodeForTag(name, node.Nodes)
            End If
        Next
        Return Nothing
    End Function

and this is called upon using

    Private Sub findSource_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles findSource.Click
        Dim fdlg As OpenFileDialog = New OpenFileDialog()

        fdlg.Title = "Browse for a Archive File"
        fdlg.InitialDirectory = "c:\"
        fdlg.FilterIndex = 2
        fdlg.RestoreDirectory = True
        If fdlg.ShowDialog() = DialogResult.OK Then
            sourceFilePath.Text = fdlg.FileName
            PopulateTreeView(fdlg.FileName)
        End If
    End Sub
Thanks a lot for the help!

Coordinator
Jul 7, 2009 at 2:54 PM

No problem.

Here's what it looks like:

and here's a Visual Studio project that you can download and run - uses VS2008. It's very basic, it shows how to populate a TreeView with the contents of a ZipFile.