Click here to Skip to main content
15,887,746 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
I have been completely stuck for the past two weeks on this. I have to create a winform that shows directories and files in a treeview. I got the majority of it working (aside from loading fake nodes in to show plus/minus signs but that's okay for now) but the issue that I am having is that I have to run the script twice in order for the form load event to start loading data into the treeview. Typically, I would just do an addrange for the treenodecollection, however my client wants the the size of the folder and files to be displayed in the title. This is what I have:

<blockquote class="quote"><div class="op">Quote:</div>
[cmdletbinding()]
param(
    [string]$TargetPath = (get-item .).fullname
    )

#region Load Assemblies

[Void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[Void][System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[Void][System.Reflection.Assembly]::LoadWithPartialName("System.ServiceProcess")
[Void][System.Reflection.Assembly]::LoadWithPartialName("System.Design")
[void][System.Reflection.Assembly]::LoadWithPartialName('System.Collections.Generic')
[void][System.Reflection.Assembly]::LoadWithPartialName('System.Collections')
[void][System.Reflection.Assembly]::LoadWithPartialName('System.ComponentModel')
[void][System.Reflection.Assembly]::LoadWithPartialName('System.Text')
[void][System.Reflection.Assembly]::LoadWithPartialName('System.Data')
#endregion Load Assemblies


$ErrorActionPreference = 'continue' #'silentlycontinue'


#region Generated Form Objects

function InitializeComponent {

[System.Windows.Forms.Application]::EnableVisualStyles()
$MainForm = [System.Windows.Forms.Form]::new()
$TreeView = [System.Windows.Forms.Treeview]::new()
$RootNode = [System.Windows.Forms.TreeNode]::new()
$ContextMenu = [System.Windows.Forms.ContextMenuStrip]::new()
$PathMenuItem = [System.Windows.Forms.ToolStripMenuItem]::new()
$imagelist = [System.Windows.Forms.ImageList]::new()
$MainForm.SuspendLayout()

#Treeview
$TreeView.Location = '0, 0' 
$TreeView.Name = 'TreeView'
$TreeView.ImageIndex =  1
$TreeView.Size = '500, 900'#'296, 640'
$TreeView.Dock = 'Fill'
$TreeView.TabIndex = 3
$TreeView.SelectedImageIndex = 1
#$TreeView.Sorted = $true
$TreeView.AutoSize = $true
$TreeView.Font = 'Century Gothic, 12pt' #8.25pt'
#$TreeView.BorderStyle = 'None'
$TreeView.add_BeforeExpand($TreeView_BeforeExpand)
$TreeView.add_NodeMouseClick($TreeView_NodeMouseClick)
$TreeView.add_NodeMouseDoubleClick($TreeView_NodeMouseDoubleClick)

#RootNode
$RootNode.Name = 'Root'
$RootNode.Text = "Selected Path: $((Get-location).Path)"
$RootNode.Tag = "Directory"
[void]$TreeView.Nodes.Add($RootNode)

#ContextMenu
$ContextMenu.Name = "ContextMenu"
$ContextMenu.Size = '170, 26'
$ContextMenu.add_ItemClicked($ContextMenu_ItemClicked)
#[Void]$ContextMenu.Items.Add($PathMenuItem) 

#PathMenuItem
$PathMenuItem.Name = "PathMenuItem"
$PathMenuItem.Size = '169, 22'
$PathMenuItem.Text = "Change Path"
$PathMenuItem.Font = 'Century Gothic, 8.25pt'
$PathMenuItem.add_Click($PathMenuItem_Click)

#MainForm
$MainForm.ClientSize = '1440, 1000' #'1000, 800'
$MainForm.StartPosition = 'CenterScreen'
$MainForm.Text = "TreeView Disk Beta"
$MainForm.Padding = "0,0,0,0"
$MainForm.Font = [System.Drawing.Font]::new("Segoe UI",9,[System.Drawing.FontStyle]::Regular,[System.Drawing.GraphicsUnit]::Pixel) #'Segoe UI, 9pt'
$MainForm.FormBorderStyle = 'Sizable'
$MainForm.AutoSize = $true
$MainForm.AutoScaleDimensions = '6, 13'
#$MainForm.AutoScaleBaseSize = '6, 13'
$MainForm.AutoScaleMode = 'Font' #'Dpi'
$MainForm.Controls.Add($TreeView)
$MainForm.add_Load($MainForm_Load)
$MainForm.add_Load($Form_StateCorrection_Load)
#$MainForm.Add_Shown($MainForm_Shown)
$MainForm.add_FormClosing($Form_Cleanup_FormClosing)



#Save the initial state of the form
$InitialFormWindowState = $MainForm.WindowState
#$MainForm.ResumeLayout($false)
$MainForm.ResumeLayout()

}

 . InitializeComponent

#endregion Generated Form Objects

#region Functions

# Gets Size in human readable format for File and Folder Objects
function Get-FriendlySize {
    param($Bytes)
    $sizes='Bytes,KB,MB,GB,TB,PB,EB,ZB' -split ','
    for($i=0; ($Bytes -ge 1kb) -and 
        ($i -lt $sizes.Count); $i++) {$Bytes/=1kb}
        $N=2; if($i -eq 0) {$N=0}
            "{0:N$($N)} {1}" -f $Bytes, $sizes[$i]
} #Ref: https://martin77s.wordpress.com/2017/05/20/display-friendly-file-sizes-in-powershell

# Calls to change the path when right-click on Treeview
Function Change-TreePath {
$FolderBrowserDialog = [System.Windows.Forms.FolderBrowserDialog]::new()
$FolderBrowserDialog.ShowNewFolderButton = $false
$FolderBrowserDialog.Description = 'Select a Target Path'

if ($FolderBrowserDialog.ShowDialog() -eq 'OK') {
    $TargetPath = $FolderBrowserDialog.SelectedPath
    New-Variable -Name TargetPath -Value $TargetPath -Scope 1 -Force
    $TreeView.Nodes.Clear()
    $PathNode = [System.Windows.Forms.TreeNode]::new()
    $PathNode.Text = "Selected Path: $((Get-Item -Path $TargetPath).FullName)"
    [void]$TreeView.Nodes.Add($PathNode)
    & $MainForm_Load 
            }
    #[void]$FolderBrowserDialog.ShowDialog()
}

# Scriptblock for coloring nodes for Hidden Files and Folders
$ColorNode = {
    Foreach ($node in $TreeView.SelectedNode.Nodes) {
         if ($node.Tag -contains "hidden") {
            $node.ForeColor = [System.Drawing.Color]::OrangeRed 
        }
    }
}

Function InvokeAddNodes {
param($selectedNode, $IOPath)

gci -Path $IOPath -Force -ErrorAction SilentlyContinue | Foreach {
    if ($_ -is [System.IO.FileInfo]) {
    $ImageFiles = switch -Regex ($_.Extension) {
             "^\.ini|\.dll|\.sys"                         {5}
             "^\.exe|\.msi"                               {2}
             "^.jpg|\.png|\.bmp|\.ico|\.jpeg|\.fav|\.svg" {3}
             "^\.zip|\.rar|\.7z"                          {7}
             "\.txt|\.rtf|\.log|\.msg"                    {6}
             default                                      {0}
                }
    $subnode = [System.Windows.Forms.TreeNode]::new()
    $subnode.Name = $_.FullName
    $subnode.Text = & { ($_.Name + " " + (Get-FriendlySize -Bytes $_.Length) -as [string]) }
    $subnode.Tag = @(($_.Attributes).ToString().Split(",").Replace(" ", ""))
    $subnode.ImageIndex = $ImageFiles
    $subnode.SelectedImageIndex = $ImageFiles
                }
    if ($_ -is [System.IO.DirectoryInfo]) {
      $DirInfo =  $_ | gci -Force -ErrorAction SilentlyContinue | Measure-Object -Sum length -ErrorAction SilentlyContinue 
      $subnode = [System.Windows.Forms.TreeNode]::new()
      $subnode.Name = $_.FullName
      $subnode.Tag = @(($_.Attributes).ToString().Split(",").Replace(" ", ""))
      $subnode.ImageIndex = 1
      $subnode.SelectedImageIndex = 1
      if ($DirInfo.Count -gt 1) {     
            $subnode.Text = & { ($_.Name + " " + (Get-FriendlySize -Bytes $DirInfo.Sum)) -as [String] }
                        }
      else {
            $subnode.Text = & { ($_.Name + " " + (Get-FriendlySize -Bytes $_.Length)) -as [String] }
            }
        }
      [void]$SelectedNode.Nodes.Add($subnode)
    }

}

#endregion Functions


#region Event Handles

#region MainForm Events

$Form_StateCorrection_Load = {
    
        #Correct the initial state of the form to prevent the .Net maximized form issue
        $MainForm.WindowState = $InitialFormWindowState
    }

$MainForm_Load = {
   $TreeView.SelectedNode = $TreeView.Nodes[0]
    Try {
    #$MainForm.Cursor = 'WaitCursor'
    $TreeView.BeginUpdate()    
         gci $TargetPath -Force | Foreach {
            if ($_ -is [System.IO.FileInfo]) {
                $ImageFiles = switch -Regex ($_.Extension) {
                     "^\.ini|\.dll|\.sys"                         {5}
                     "^\.exe|\.msi"                               {2}
                     "^.jpg|\.png|\.bmp|\.ico|\.jpeg|\.fav|\.svg" {3}
                     "^\.zip|\.rar|\.7z"                          {7}
                     "^\.txt|\.rtf|\.log|\.msg"                   {6}
                     default                                      {0} 
                        }
                    
        $subnode = [System.Windows.Forms.TreeNode]::new()
        $subnode.Name = $_.FullName
        $subnode.Text = & { ($_.Name + " " + (Get-FriendlySize -Bytes $_.Length) -as [string]) }
        $subnode.Tag = @(($_.Attributes).ToString().Split(",").Replace(" ", ""))
        $subnode.ImageIndex = $ImageFiles
        $subnode.SelectedImageIndex = $ImageFiles
                    }
        if ($_ -is [System.IO.DirectoryInfo]) {
          $DirInfo =  $_ | gci -recurse -Force -ErrorAction SilentlyContinue | Measure-Object -Sum length -ErrorAction SilentlyContinue 
          $subnode = [System.Windows.Forms.TreeNode]::new()
          $subnode.Name = $_.FullName
          $subnode.Tag = @(($_.Attributes).ToString().Split(",").Replace(" ", ""))
          $subnode.ImageIndex = 1
          $subnode.SelectedImageIndex = 1
          if ($DirInfo.Count -gt 1) {     
                $subnode.Text = & { ($_.Name + " " + (Get-FriendlySize -Bytes $DirInfo.Sum)) -as [String] }
                            }
          else {
                $subnode.Text = & { ($_.Name + " " + (Get-FriendlySize -Bytes $_.Length)) -as [String] }
                     }
                }
          [void]$TreeView.Nodes[0].Nodes.Add($subnode)
            }

      & $ColorNode
      $TreeView.EndUpdate()
      $TreeView.Refresh()
      $TreeView.SelectedNode.Expand() 
      }
    Catch [Exception]{}
   # Finally {
    #    $mainForm.Cursor = 'Default'
     #       }
 
}

#endregion MainForm Events

#region TreeView & TreeNodes Events

$treeview_BeforeExpand = [System.Windows.Forms.TreeViewCancelEventHandler] {   
    if((-not $_.Node.IsExpanded) -and ($_.Node.Tag -contains "Directory") -and ($_.Node.Clicks -eq 2)) {
        $_.Node.Expand()
            }
    else { $_.Node.Collapse() }
}
 
$TreeView_NodeMouseClick=[System.Windows.Forms.TreeNodeMouseClickEventHandler] {
    $TreeView.SelectedNode = $_.Node
    if ($_.Button -eq 'Right') {  
        $point = [System.Drawing.Point]::new(($RootNode.Bounds.X + 60), $RootNode.Bounds.Y)
        $ContextMenu.Items.Clear()
        [Void]$ContextMenu.Items.Add($PathMenuItem)  
        $ContextMenu.Show($TreeView, $point.X, $point.Y)                         
                } 
    if ((-not $_.Node.IsExpanded) -and (-not $_.Node.Nodes) -and ($_.Node.Tag -contains "Directory") ) {      
        $TreeView.SelectedNode.Nodes.Clear()     
        $TreeView.BeginUpdate()      
        InvokeAddNodes -SelectedNode $TreeView.SelectedNode -IOPath $TreeView.SelectedNode.Name 
        & $ColorNode
        $TreeView.EndUpdate()
        $TreeView.Refresh() 
             }                   
}
            
$TreeView_NodeMouseDoubleClick = [System.Windows.Forms.TreeNodeMouseClickEventHandler] {
    if (($_.Button -eq 'Left') -and (-not $_.Node.IsExpanded) -and ($_.Node.Tag -contains "Directory")) {
    Try {
       $msg = [bool][System.IO.DirectoryInfo]::new($_.Node.Name).GetFileSystemInfos()
       if (!$msg) {
           [System.Windows.Forms.MessageBox]::Show("Directory: $($_.Node.Name) is empty.", " Empty Folder")           
                        }
            }
    Catch [Exception] {
            $errmsg = $PSItem.Exception.Message
           [System.Windows.Forms.MessageBox]::Show($errmsg , " Access Denied")     
                }
        }
}

#endregion TreeView & TreeNodes Events

#region FolderBrowserDialog for Changing Paths

$PathMenuItem_Click = {
    Change-TreePath
}

#endregion FolderBrowserDialog for Changing Paths

#region Closing and Cleaning Form & Controls

$Form_Cleanup_FormClosing = [System.Windows.Forms.FormClosingEventHandler] {

    #Remove all event handlers from the controls and releases garbage collection
    try
    {
        $TreeView.remove_BeforeCheck($TreeView_BeforeCheck)
        $TreeView.remove_AfterCheck($TreeView_AfterCheck)
        $TreeView.remove_BeforeExpand($TreeView_BeforeExpand)
        $TreeView.remove_NodeMouseClick($TreeView_NodeMouseClick)
        $TreeView.remove_DoubleClick($TreeView_DoubleClick)
        $MainForm.remove_Load($MainForm_Load)
        $MainForm.remove_Shown($MainForm_Shown)
        $MainForm.remove_Load($Form_StateCorrection_Load)
        $MainForm.remove_FormClosing($Form_Cleanup_FormClosing)
        $PathMenuItem.remove_Click($PathMenuItem_Click)
        $TreeView.Nodes.Clear()
        $TreeView.Dispose()
        #$thisIO.Dispose(); rv thisIO       
        $MainForm.Dispose()
        rv TreeView
        rv RootNode
        rv TreeView_NodeMouseClick
        rv TreeView_NodeMouseDoubleClick
        rv MainForm 
        rv ContextMenu
        rv iconImage
        rv treeview_BeforeExpand
        rv Form_StateCorrection_Load 
        rv imagelist
        rv PathMenuItem  
        [System.GC]::Collect()
        $Error.Clear()   

 
    }
    catch [Exception]
    { }
}

#endregion Closing and Cleaning Form & Controls

#endregion Event Handles


#Show Disk Tree Form

$null = $MainForm.ShowDialog()

</blockquote>


What I have tried:

I have tried using in both my MainForm_Load and InvokeAddNodes Function using straight .net instead of get-childitem to speed it up:
<blockquote class="quote"><div class="op">Quote:</div>
$nodecollection =[System.Collections.ArrayList]::new()

  Foreach ($node in ([System.IO.DirectoryInfo]::new($TargetPath).GetFileSystemInfos())) {
    
    if ($node -is [System.IO.FileInfo]) {
    $subnode = [System.Windows.Forms.TreeNode]::new()
    $subnode.Name = $node.FullName
    $subnode.Text = & { ($node.Name + " " + (Get-FriendlySize -Bytes $node.Length) -as [string]) }
    $subnode.Tag = @(($node.Attributes).ToString().Split(",").Replace(" ", ""))
    $nodecollection.Add($subnode)
                }
    if ($node -is [System.IO.DirectoryInfo]) {
      $subnode = [System.Windows.Forms.TreeNode]::new()
      $DirInfo = [System.IO.DirectoryInfo]::new($node.FullName).GetFileSystemInfos() | Measure-Object -Sum length -ErrorAction SilentlyContinue 
      $subnode.Name = $node.FullName
      $subnode.Tag = @(($node.Attributes).ToString().Split(",").Replace(" ", ""))
      if ($DirInfo.Count -gt 1) {     
            $subnode.Text = & { (($node.Name).ToString() + " " + (Get-FriendlySize -Bytes $DirInfo.Sum)) -as [String] }
            $nodecollection.Add($subnode)         
                        }
      else {
            $subnode.Text = & { (($_.Name).ToString() + " " + (Get-FriendlySize -Bytes $_.Length)) -as [String] }
            $nodecollection.Add($subnode)
            }
        }
    }</blockquote>


However, that has given me some issues with returning blank entries and it still didn't load the treeview from first time of running the code. I have been bumping my head for a few weeks now and I give up. Any help would be appreciated!
Posted

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



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900