Monday, 16 April 2018

PowerShell WPF - Customize TreeView Icon



       I have seen many people struggle with this so I decided to write something about TreeView in PowerShell. 

The script in this post is based on the project available here (written in C#). I simply rewrote it in PowerShell and added my personal touch.  He displayed a drive image if the TreevieItem was a drive and a folder image otherwise. I extended the principle a little; you can specify directly in your script which kind of image you want to use in which case.




How do we get the WPF tree to do that?

In this example, I chose “.\Test” as root folder (available in the git repository). The tree will be established from this base.  I listed all files and folders with methods below:


$AllFiles  = [IO.Directory]::GetFiles('.\Test')
$AllDirectory = [IO.Directory]::GetDirectories('.\Test')


Next step is to create the tree itself. It is done this way:


# ================== Handle Folders ===========================
foreach ($folder in $AllDirectory){

    $treeViewItem = [Windows.Controls.TreeViewItem]::new()
    $treeViewItem.Header = $folder.Substring($folder.LastIndexOf("\") + 1)
    $treeViewItem.Tag = @("folder",$folder)
    $treeViewItem.Items.Add($dummyNode) | Out-Null
    $treeViewItem.Add_Expanded({
        TreeExpanded($_.OriginalSource)
    })
    $FolderTree.Items.Add($treeViewItem)| Out-Null
}

# ================== Handle Files ===========================
foreach ($file in $AllFiles){
    $treeViewItem = [Windows.Controls.TreeViewItem]::new()
    $treeViewItem.Header = $file.Substring($file.LastIndexOf("\") + 1)
    $treeViewItem.Tag = @("file",$file)
    $FolderTree.Items.Add($treeViewItem)| Out-Null
}


That is enough to get us the root elements of “.\Test” folder for the TreeView.
Now when we expand an item we would like to show the child nodes of the selected item.

I created a function “TreeExpand()” which is called every time a TreeViewItem is expanded. Its aim is to list all folders and files inside the selected item (current folder) and repeat the process recursively.  


Function TreeExpanded($sender){
    $item = [Windows.Controls.TreeViewItem]$sender
   
    If ($item.Items.Count -eq 1 -and $item.Items[0] -eq $dummyNode)
    {
        $item.Items.Clear();
        Try
        {
           
     foreach ($string in [IO.Directory]::GetDirectories($item.Tag[1].ToString()))
     {
           $subitem = [Windows.Controls.TreeViewItem]::new();
           $subitem.Header = $string.Substring($string.LastIndexOf("\") + 1)
           $subitem.Tag = @("folder",$string)
           $subitem.Items.Add($dummyNode)
           $subitem.Add_Expanded({
               TreeExpanded($_.OriginalSource)
           })
           $item.Items.Add($subitem) | Out-Null
     }

     foreach ($file in [IO.Directory]::GetFiles($item.Tag[1].ToString())){
          $subitem = [Windows.Controls.TreeViewItem]::new()
          $subitem.Header = $file.Substring($file.LastIndexOf("\") + 1)
          $subitem.Tag = @("file",$file)
          $item.Items.Add($subitem)| Out-Null

      }

        }  
        Catch [Exception] { }
    }
    
}



By default, the WPF TreeView control does NOT display images. Next step, we will add a custom image for each TreeViewItems. So, the XAML will look like this:

<TreeView x:Name="TreeView" Width="200">
    <TreeView.Resources>
        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="HeaderTemplate">
                <Setter.Value>
                    <HierarchicalDataTemplate  >
                            <StackPanel Orientation="Horizontal">
                            <Image Name="img" 
                                Width="20" Height="20"
                                Stretch="Fill" 
                                Source= "./images/folder.png"     
                                />
                            <TextBlock Text="{Binding}"
                                Margin="5,0" />
                        </StackPanel>
                    </HierarchicalDataTemplate >
                </Setter.Value>
            </Setter>
        </Style>
    </TreeView.Resources>
</TreeView>

Which gives us this display:
Something is odd as you can see; each item has a folder for the icon. This is where it starts to get complicated. Trust me it is … well, in PowerShell but in C# it is not. Therefore, I created my own assembly to implement an “Interface” which convert a “text” into image path based on this “text”.

Something like:
file”  à./images/files.png
folderà./images/folder.png

The reason we need to go through this is to simplify the use of a dataTemplate applied on the TreeViewItem. Then we can easily provide dynamically a path (source) to an image tag for example.

To achieve this we need to make some modifications inside the XAML.

Add this line to the window tag:

xmlns:sys="clr-namespace:dev4sys.Tree;assembly=dev4sys.Tree"


And Replace the Image tag in the DataTemplate with this one:
<Image 
    Width="20" Height="20"
    Stretch="Fill" 
    Source= "{Binding
        RelativeSource={RelativeSource
        Mode=FindAncestor,
        AncestorType={x:Type TreeViewItem}},
        Path=Tag,
        Converter={sys:ObjectTagToImageConverter}}"     
    />

How does it work?

To make short, it will take the object content of the TreeViewItem.Tag and convert the value to an ImagePath.

If you remember in the script above there is this line:
  
$treeViewItem.Tag = @("folder",$folder)


The ObjectTagToImageConverter function takes this array as input. Get the text folderand convert it to “./images/folder.png.

Why an array?

Because you can add as much information as you want in this array. Here I’m using only two value in the array @(), but it can be extended and you can retrieve them easily in your PowerShell Script.

Do I need to change something in my script?

Also, do not forget to add the assembly in you PowerShell script!

[System.Reflection.Assembly]::LoadFrom('assembly\dev4sys.Tree.dll')

You can get this dll in the git repository of this project.


Now you can have the same result as presented above! This can be even extended to other uses such as an OU browser etc …

Download link
You can download all the script here and thanks for reading!




Share:

0 commentaires:

Post a comment

Popular Posts

Join us on Facebook

STATS

Contact form

Name

Email *

Message *