Consider the below. The SortListView function works perfectly if the ListView contains ungrouped items.
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$form = New-Object System.Windows.Forms.Form
$form.Text = "Test"
$form.Size = '500,375'
$form.StartPosition = 'CenterScreen'
$form.MaximizeBox = $false
# List of sessions
$LogonList = New-Object System.Windows.Forms.ListView
$LogonList.View = 'Details'
$LogonList.Location = '10,20'
$LogonList.Size = '465,305'
$LogonList.FullRowSelect = $true
$LogonList.Columns.Add('UID',80) | Out-Null
$LogonList.Columns.Add('IPAddress',100) | Out-Null
$LogonList.Columns.Add('HostName',160) | Out-Null
$LogonList.Add_ColumnClick({SortListView $this $_.Column})
$form.Controls.Add($LogonList)
#
Function SortListView {
Param(
[System.Windows.Forms.ListView]$xsender,
$column
)
$Script:SortingDescending = !$Script:SortingDescending
$xsender.Sorting = 'none'
If (!$xsender.Groups) {
#
# No groups in ListView - sort whole list by clicked column property
#
$xsender.ShowGroups = $false
$temp = $xsender.Items | Foreach-Object { $_ }
$xsender.Items.Clear()
$xsender.Items.AddRange(($temp | Sort-Object -Descending:$script:SortingDescending -Property @{ Expression={ $_.SubItems[$column].Text } }))
}
Else {
#
# ListView is grouped, sort each group by clicked column property.
#
$xsender.ShowGroups = $true
$temp = $xsender.Items | Foreach-Object { $_ }
Write-Host "ListView groups:"
$temp | Group-Object -Property Group | ForEach-Object {
Write-Host $_.Name
}
$xsender.Items.Clear()
}
}
#
$TestData = New-Object System.Collections.ArrayList
$TestData.Add([pscustomobject]@{UID='userjoe';IPAddress='192.168.150.14';Hostname='Workstation99'}) | Out-Null
$TestData.Add([pscustomobject]@{UID='userjoe';IPAddress='192.168.150.15';Hostname='Workstation100'}) | Out-Null
$TestData.Add([pscustomobject]@{UID='userjoe';IPAddress='192.168.150.16';Hostname='Workstation101'}) | Out-Null
$TestData.Add([pscustomobject]@{UID='userjoe';IPAddress='192.168.150.17';Hostname='Workstation102'}) | Out-Null
$TestData.Add([pscustomobject]@{UID='userdave';IPAddress='192.168.150.13';Hostname='Workstation104'}) | Out-Null
$TestData.Add([pscustomobject]@{UID='userdave';IPAddress='192.168.150.12';Hostname='Workstation105'}) | Out-Null
$TestData.Add([pscustomobject]@{UID='userdave';IPAddress='192.168.150.11';Hostname='Workstation106'}) | Out-Null
$TestData.Add([pscustomobject]@{UID='userdave';IPAddress='192.168.150.10';Hostname='Workstation107'}) | Out-Null
#
$TestData | Group-Object -Property UID | ForEach-Object {
$ThisEntry = New-Object System.Windows.Forms.ListViewGroup
$ThisEntry.Header = $_.Name
$LogonList.Groups.Add($ThisEntry) |Out-Null
$DuplicateUIDs = $_.Group | Sort-Object {$_.IPAddress} -Descending
$DuplicateUIDs | ForEach-Object {
$Entry = New-Object System.Windows.Forms.ListViewItem('-') -ErrorAction Stop
$Entry.SubItems.Add([string]$_.IPAddress) | Out-Null
$Entry.SubItems.Add([string]$_.HostName) | Out-Null
$Entry.Group = $ThisEntry
# Add compiled object to ListView box
$LogonList.Items.Add($Entry) | Out-Null
}
}
$form.Activate()
$form.ShowDialog() | Out-Null
$form.Dispose()
I'd like to code for the same behaviour when the items are grouped - in the above example clicking the column header should order all the group items within the groups by that column. I'm really struggling to get my head round the different objects/item assignments and come up with a sensible solution - any help appreciated.
CodePudding user response:
if you are using PowerShell 5 or above, you can achieve ListView sort in an easier and faster way :
First, implement a Comparer Interface somewhere in your code (you can add it right before your sort function) :
class ListViewItemComparer : System.Collections.IComparer
{
[int]$col
[System.Windows.Forms.SortOrder]$order
ListViewItemComparer()
{
$this.col = 0
$this.order = [System.Windows.Forms.SortOrder]::Ascending
}
ListViewItemComparer([int]$column, [System.Windows.Forms.SortOrder]$sortOrder)
{
$this.col = $column
$this.order = $sortOrder
}
[int]Compare([object]$x, [object]$y)
{
$result = [String]::Compare( `
([System.Windows.Forms.ListViewItem]$x).SubItems[$this.col].Text,`
([System.Windows.Forms.ListViewItem]$y).SubItems[$this.col].Text);
if ($this.order -eq [System.Windows.Forms.SortOrder]::Ascending)
{
return $result
}
else
{
return -($result)
}
}
}
then your sort function will be as simple by setting the ListViewItemSorter Property to a new comparer interface :
Function SortListView {
Param(
[System.Windows.Forms.ListView]$xsender,
$column
)
$Script:SortingDescending = !$Script:SortingDescending
if ($Script:SortingDescending)
{
$xsender.Sorting = [System.Windows.Forms.SortOrder]::Descending
}
else
{
$xsender.Sorting = [System.Windows.Forms.SortOrder]::Ascending
}
$xsender.ListViewItemSorter = [ListViewItemComparer]::new($column, $xsender.Sorting)
}
This works whatever there is a group or not.
some more information here and here
EDIT :
Do not forget this line before creating your form
Add-Type -AssemblyName System.Windows.Forms
If you are using an old version of PowerShell, implement the compare interface with C# this way :
$compareCode = @"
using System;
using System.Collections;
using System.Windows.Forms;
namespace Tools
{
public class ListViewItemComparer : IComparer
{
private int col;
private SortOrder order;
public ListViewItemComparer()
{
col = 0;
order = SortOrder.Ascending;
}
public ListViewItemComparer(int column, SortOrder sortOrder)
{
col = column;
order = sortOrder;
}
public int Compare(object x, object y)
{
int result = String.Compare(
((ListViewItem)x).SubItems[col].Text,
((ListViewItem)y).SubItems[col].Text);
if (order == SortOrder.Ascending)
{
return result;
}
else
{
return -result;
}
}
}
}
"@
Add-Type -TypeDefinition $compareCode -ReferencedAssemblies System.Windows.Forms
then call the interface in your sort function (change only this line) :
$xsender.ListViewItemSorter = [Tools.ListViewItemComparer]::new($column, $xsender.Sorting)
EDIT 2
The PowerShell class option needs to call Add-Type -AssemblyName System.Windows.Forms
BEFORE you run the script in order to work correctly. This is due to the way PowerShell is parsing code before execution.
#Call script
Add-Type -AssemblyName System.Windows.Forms
.\MyFormScriptWithICompareClass.ps1
The C# version does not have this issue.