2

I have a ton of Nodes in my TreeView, and have a textbox that filters through them to highlight the matched search. However, its a bit messy as it shows all the other nodes, and after I change my search, it leaves all nodes expanded.

I am trying to make something like this, https://www.codeproject.com/Tips/1000621/Filtering-and-Hiding-Tree-Nodes-WinForms But I am using Windows forms / Powershell ISE and seem to struggle with implementing it into my own code.

For closing nodes I tried using things along the line of (Textbox.textlength -eq 0) to trigger a close all nodes function, but that was not working.

Here is what I want it too look like. Left is what I want, Right is what mine looks like. enter image description here

Here is an example of the search function I am using.

Add-Type -AssemblyName System.Windows.Forms

function GetNodes([System.Windows.Forms.TreeNodeCollection] $nodes)
{
     foreach ($n in $nodes) {
        $n
        GetNodes($n.Nodes)
     }
}

$form = New-Object System.Windows.Forms.Form
$form.Text ="Test"
$form.Controls.AddRange(@(
    ($txt = [System.Windows.Forms.TextBox] @{
        Location = [System.Drawing.Point]::new(8, 8);
        Width = 100;
    }),
    ($btn = [System.Windows.Forms.Button] @{
        Location = [System.Drawing.Point]::new(120, 8);
        Width = 50;
        Text = "Search";
    }),
    ($tree = [System.Windows.Forms.TreeView] @{
        Location = [System.Drawing.Point]::new(8, 40);
        Width = 170;
        HideSelection = $false
    })
))
$form.AcceptButton= $btn

$tree.Nodes.Add("A1", "A1")
$tree.Nodes.Add("A2", "A2")
$tree.Nodes[0].Nodes.Add("A11", "A11")
$tree.Nodes[0].Nodes.Add("A12", "A12")
$tree.Nodes[1].Nodes.Add("A21", "A21")
$tree.Nodes[1].Nodes.Add("A22", "A22")

$btn.Add_Click({param($sender,$e)
    $nodes = GetNodes($tree.Nodes)
    foreach ($node in $nodes) {
        if($node.Text -like $txt.Text){
            $tree.SelectedNode = $node
            $node.EnsureVisible()
            break
        }
     }
})
$form.ShowDialog() | Out-Null
$form.Dispose()
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
DeadLink
  • 93
  • 12

1 Answers1

4

Assuming you are searching on a data source like a folder structure, this is what I'll do:

  • Create a function to get list of all directories recursively into a list
  • Create a function to filter the list of directories and return a list of directories which contain a specific text in their names.
  • Create a function to populate treeview
  • create a function to highlight treenode if it contains a specific text

Then in the text-changed event of the textbox, I'll filter and highlight tree:

enter image description here

Here is the code:

Add-Type -AssemblyName System.Windows.Forms

function GetPaths($root)
{
    Get-ChildItem $root -Recurse -Directory | % {
        $_.FullName.Replace($root, "").Trim("\")} 
}

function FilterPaths($paths, $like)
{
    $paths | ? {$_ -like "*$like*"} | % {
        $i = $_.LastIndexOf("$like", [System.Globalization.CompareOptions]::IgnoreCase)
        if($i -gt -1) {
            $j = $_.IndexOf("\", $i, [System.Globalization.CompareOptions]::IgnoreCase)
            if($j -gt -1) {
                $_.SubString(0,$j)
            } else {
                $_
            }
        }
    } 
}

function GetNodes($nodes)
{
     foreach ($n in $nodes) {
        $n
        GetNodes($n.Nodes)
     }
}

function HighlightNodes($nodes, $like)
{
    if(!$like){ return }
    $nodes | ? {$_ -like "*$like*"} | % {
            $_.BackColor = "Yellow"
    }
}

function PopulateTree($treeView, $paths)
{
    $treeView.Nodes.Clear()
    foreach ($path in $paths)
    {
        $lastNode = $null
        $subPathAgg = ""
        foreach ($subPath in ($path -split '\\'))
        {
            $subPathAgg += ($subPath + '\')
            $nodes = $treeView.Nodes.Find($subPathAgg, $true)
            if ($nodes.Length -eq 0) {
                if ($lastNode -eq $null) {
                    $lastNode = $treeView.Nodes.Add($subPathAgg, $subPath)
                } else {
                    $lastNode = $lastNode.Nodes.Add($subPathAgg, $subPath)
                }
            } else {
                $lastNode = $nodes[0]
            }
        }
    }
}

$form = New-Object System.Windows.Forms.Form
$form.Text ="Test"
$form.Controls.AddRange(@(
    ($txt = [System.Windows.Forms.TextBox] @{
        Location = [System.Drawing.Point]::new(8, 8);
        Width = $form.ClientSize.Width - 16;
        Anchor = [System.Windows.Forms.AnchorStyles]13
    }),
    ($tree = [System.Windows.Forms.TreeView] @{
        Location = [System.Drawing.Point]::new(8, 40);
        Width = $form.ClientSize.Width - 16;
        Anchor = [System.Windows.Forms.AnchorStyles]15
        Height = 200;
        HideSelection = $false
    })
))
$form.AcceptButton= $btn
$root = "C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\ItemTemplates\CSharp"
$paths = GetPaths $root
PopulateTree $tree $paths
$tree.ExpandAll()

$txt.Add_TextChanged({param($sender,$e)
    $tree.BeginUpdate()
    $like = $txt.Text
    $filtered = FilterPaths $paths $like
    PopulateTree $tree $filtered
    HighlightNodes (GetNodes $tree.Nodes) $like
    $tree.ExpandAll()
    $tree.TopNode = $tree.Nodes[0]
    $tree.EndUpdate()
})
$form.ShowDialog() | Out-Null
$form.Dispose()
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • Repopulating the tree was what I was missing. I was trying to go off the data I had, and not changing the data and continuing to parse it out. You're a saint, thank you so much. You have helped me so much. – DeadLink Feb 02 '20 at 18:55
  • any tips for having it search Nodes and not just a path. This tool is meant to search the folders that has our install executable, but those folders have a bunch of mini folders which makes loading this take forever. I have all my nodes working and setup, but cant seem to get it to search $nodes, only $path. – DeadLink Feb 03 '20 at 19:31
  • (1) This example loads the initial data from file system, but it doesn't really care about file system. Everything than can be modeled as a path can be used as data source, for example list of sites in IIS and so on. (2) Searching between paths will be definitely faster than searching between nodes. (3) There is no way in TreeView to hide nodes, so you need to recreate tree. (4) You can make the `FilterPaths` function a bit faster by just keeping it as `$paths | ? {$_ -like "*$like*"}`. – Reza Aghaei Feb 03 '20 at 19:39
  • I added to the GetPaths function, -Depth 2 , and that basically fixed what I was trying to accomplish, I have some folders I need to go one more level into, but it works enough for me. Thanks for the help, I will stay away from searching nodes. – DeadLink Feb 03 '20 at 20:31
  • Cool! No problem :) – Reza Aghaei Feb 03 '20 at 20:33