Spotlight on the ListView Control – Part 3

April 11th, 2012 by David Corrales
Last updated on April 18th, 2012

 

The “Spotlight on Controls” series focuses on a single WinForms control in PrimalForms 2011 , details the important Properties, Methods, and Events of the control and demonstrates how to utilize the control. Most of the information about the controls is still applicable to previous versions of PrimalForms.

In Part 2 of the Spotlight on the ListView Control, we covered the control’s properties as well as most of the methods and events. In part 3 we will cover how you can sort items and utilize various helper functions.

Sorting ListViewItems:

The ListView supports basic sorting, but it only handles sorting by the first column. In case you want to sort using the content of other columns, you need to write some additional code. Typically you want to sort when a user clicks on a column header, so we will be using the ColumnClick event to trigger sorting.

Simple Solution:

The simplest way to change the sort items is to toggle the Sort property:

$listview1_ColumnClick=[System.Windows.Forms.ColumnClickEventHandler]{ 
    #Event Argument: $_ = [System.Windows.Forms.ColumnClickEventArgs]
    if($this.Sorting -eq'Ascending') 
    { 
        $this.Sorting ='Descending' 
    } 
}

The problem with this simplistic approach is that it doesn’t take into account which column was clicked. In this case, the Sorting property just doesn’t cut it, unless you only have one column.

Possible Alternative Solution:

Next you can try to create you own sort procedure in PowerShell depending on the column clicked.

You can manually sort the items by doing the following:

$listview1_ColumnClick=[System.Windows.Forms.ColumnClickEventHandler]{
#Event Argument: $_ = [System.Windows.Forms.ColumnClickEventArgs]

    $array = New-Object System.Collections.ArrayList

    foreach($item in $listview1.Items)
    {
        $psobject = New-Object PSObject 
        $psobject | Add-Member -type 'NoteProperty' -Name 'Text'`
        -value $item.SubItems[$_.Column].Text
        $psobject | Add-Member -type 'NoteProperty' -Name 'ListItem'`
        -value $item
        $array.Add($psobject)    
    }
    
    $array = ($array | Sort-Object -Property Text)
    
    $listview1.BeginUpdate()
    
    $listview1.Items.Clear();
    
    foreach($item in $array)
    {
        $listview1.Items.Add($item.ListItem)    
    }
    
    $listview1.EndUpdate();
}

Note: When you manually sorting the List, you will want to set the Sorting property to None.

In this sample, we are creating custom objects and adding them to a list. We use the custom property to sort our list and after we add the sorted Items back into the ListView. The down side to this method is that you lose any group associations, therefore you need to reassign them. Plus this method isn’t the most efficient way of sorting your items. You are probably better off sorting the original data and rebuilding the ListView again.

Final Solution:

The best solution is to use a ListViewItemSorter class. Unfortunately it requires you to create a custom C# class that will handle the item comparisons. Fortunately for you, we wrote our own custom class to handle the sorting for you. The get around creating a class in PowerShell (something it doesn’t support), we have to use Add-Type cmdlet. The Add-Type cmdlet dynamically compiles C# code and allows you to use the new class directly in PowerShell.

    Add-Type -ReferencedAssemblies ('System.Windows.Forms') -TypeDefinition  @" 
    using System;
    using System.Windows.Forms;
    using System.Collections;
    public class ListViewItemComparer : IComparer
    {
        public int column;
        public SortOrder sortOrder;
        public ListViewItemComparer()
        {
            column = 0;
            sortOrder = SortOrder.Ascending;
        }
        public ListViewItemComparer(int column, SortOrder sort)
        {
            this.column = column;
            sortOrder = sort;
        }
        public int Compare(object x, object y)
        {
            if(column >= ((ListViewItem)x).ListView.Columns.Count ||
                column >= ((ListViewItem)x).SubItems.Count ||
                column >= ((ListViewItem)y).SubItems.Count)
                column = 0;
        
            if(sortOrder == SortOrder.Ascending)
                return String.Compare(((ListViewItem)x).SubItems[column].Text,`
 ((ListViewItem)y).SubItems[column].Text);
            else
                return String.Compare(((ListViewItem)y).SubItems[column].Text,`
 ((ListViewItem)x).SubItems[column].Text);
        }
    }
"@  | Out-Null
 

Our custom ListViewItemComparer class will act as a generic sorting class that will allow you to compare any column and choose in which direction to sort.

Next we created a PowerShell function that will allow you to sort the ListView by Column Index:

function Sort-ListViewColumn 
{
    <#
    .SYNOPSIS
        Sort the ListView's item using the specified column.

    .DESCRIPTION
        Sort the ListView's item using the specified column.
        This function uses Add-Type to define a class that sort the items.
        The ListView's Tag property is used to keep track of the sorting.

    .PARAMETER ListView
        The ListView control to sort.

    .PARAMETER ColumnIndex
        The index of the column to use for sorting.
        
    .PARAMETER  SortOrder
        The direction to sort the items. If not specified or set to None, it will toggle.
    
    .EXAMPLE
        Sort-ListViewColumn -ListView $listview1 -ColumnIndex 0
#>
    param(    
            [ValidateNotNull()]
            [Parameter(Mandatory=$true)]
            [System.Windows.Forms.ListView]$ListView,
            [Parameter(Mandatory=$true)]
            [int]$ColumnIndex,
            [System.Windows.Forms.SortOrder]$SortOrder = 'None')
    
    if(($ListView.Items.Count -eq 0) -or ($ColumnIndex -lt 0) -or ($ColumnIndex -ge $ListView.Columns.Count))
    {
        return;
    }
    
    #region Define ListViewItemComparer
        try{
        $local:type = [ListViewItemComparer]
    }
    catch{
    Add-Type -ReferencedAssemblies ('System.Windows.Forms') -TypeDefinition  @" 
    using System;
    using System.Windows.Forms;
    using System.Collections;
    public class ListViewItemComparer : IComparer
    {
        public int column;
        public SortOrder sortOrder;
        public ListViewItemComparer()
        {
            column = 0;
            sortOrder = SortOrder.Ascending;
        }
        public ListViewItemComparer(int column, SortOrder sort)
        {
            this.column = column;
            sortOrder = sort;
        }
        public int Compare(object x, object y)
        {
            if(column >= ((ListViewItem)x).ListView.Columns.Count ||
                column >= ((ListViewItem)x).SubItems.Count ||
                column >= ((ListViewItem)y).SubItems.Count)
                column = 0;
        
            if(sortOrder == SortOrder.Ascending)
                return String.Compare(((ListViewItem)x).SubItems[column].Text,`
 ((ListViewItem)y).SubItems[column].Text);
            else
                return String.Compare(((ListViewItem)y).SubItems[column].Text,`
 ((ListViewItem)x).SubItems[column].Text);
        }
    }
"@  | Out-Null
    }
    #endregion
    
    if($ListView.Tag -is [ListViewItemComparer])
    {
        #Toggle the Sort Order
        if($SortOrder -eq [System.Windows.Forms.SortOrder]::None)
        {
            if($ListView.Tag.column -eq $ColumnIndex -and $ListView.Tag.sortOrder -eq 'Ascending')
            {
                $ListView.Tag.sortOrder = 'Descending'
            }
            else
            {
                $ListView.Tag.sortOrder = 'Ascending'
            }
        }
        else
        {
            $ListView.Tag.sortOrder = $SortOrder
        }
        
        $ListView.Tag.column = $ColumnIndex
        $ListView.Sort()#Sort the items
    }
    else
    {
        if($Sort -eq [System.Windows.Forms.SortOrder]::None)
        {
            $Sort = [System.Windows.Forms.SortOrder]::Ascending    
        }
        
        #Set to Tag because for some reason in PowerShell ListViewItemSorter prop returns null
        $ListView.Tag = New-Object ListViewItemComparer ($ColumnIndex, $SortOrder) 
        $ListView.ListViewItemSorter = $ListView.Tag #Automatically sorts
    }
}

The first part checks if the ListViewItemComparer types exists. If it doesn’t then it will use the Add-Type cmdlets to add the custom class. Next the functions stores new sorter class in the ListView’s Tag property, so it can track which direction and column it was lasted sorted. This way it can easily toggle between Ascending and Descending when you click the same column.

The last step is to simply call the Sort-ListViewColumn function from ColumnClick event:

$listviewApplications_ColumnClick=[System.Windows.Forms.ColumnClickEventHandler]{
#Event Argument: $_ = [System.Windows.Forms.ColumnClickEventArgs]
    Sort-ListViewColumn $this $_.Column
}

Now you need not worry about creating your own custom sorting script. Just call Sort-ListViewColumn and your done.

Sorted Columns:

Sorted Column 1Sorted Column 2

ListView Help Function:

Here is a help function to add items to the ListView. It will handle the creation of ListViewItems for you.

function Add-ListViewItem
{

    Param( 
    [ValidateNotNull()]
    [Parameter(Mandatory=$true)]
    [System.Windows.Forms.ListView]$ListView,
    [ValidateNotNull()]
    [Parameter(Mandatory=$true)]
    $Items,
    [int]$ImageIndex = -1,
    [string[]]$SubItems,
    [System.Windows.Forms.ListViewGroup]$Group,
    [switch]$Clear)
    
    if($Clear)
    {
        $ListView.Items.Clear();
    }
    
    if($Items -is [Array])
    {
        foreach ($item in $Items)
        {        
            $listitem  = $ListView.Items.Add($item.ToString(), $ImageIndex)
            #Store the object in the Tag
            $listitem.Tag = $item
            
            if($SubItems -ne $null)
            {
                $listitem.SubItems.AddRange($SubItems)
            }
            
            if($Group -ne $null)
            {
                $listitem.Group = $Group
            }
        }
    }
    else
    {
        #Add a new item to the ListView
        $listitem  = $ListView.Items.Add($Items.ToString(), $ImageIndex)
        #Store the object in the Tag
        $listitem.Tag = $Items
        
        if($SubItems -ne $null)
        {
            $listitem.SubItems.AddRange($SubItems)
        }
        
        if($Group -ne $null)
        {
            $listitem.Group = $Group
        }
    }
}

Note: These helper functions are already included in PrimalForms 2011 since v2.0.20.

We also created a sample form demonstrates the ListView and column sorting. You can download the ListView sample from our Downloads section.

 

 
[Google+]   [Facebook]   [LinkedIn]   [StumbleUpon]   [Digg]   [Reddit]   [Google Bookmark]  

Tags: , , , ,