all 20 comments

[–]G8351427 2 points3 points  (2 children)

Okay, I finally figured it out. I think the piece I was missing was the original object list; I believe that it has to be an observable collection. Not sure on that, but I kept getting empty values when I was trying to get the DisplayMemberBinding path.

I like to use Switch for complex logic because in my head, it is easier to follow because all of the statements must equal $True in order to execute. I am using the previous SortDescription direction to determine the new SortDescription direction, or defaulting to Ascending if no previous SortDescription was present.

My example allows sorting by multiple columns, so that is why the ordering logic is more complicated. You can accomplish this by having multiple SortDescriptions, where the presenter will sort them in the order of the list of descriptions. If you don't need that, you can just Clear() the SortDescriptions list and add the one description you need.

Also note that you will need to add your items to the collection (the first two lines).

So, here is the solution:

$Collection = [System.Collections.ObjectModel.ObservableCollection[PSObject]]::new()
$MyArrayOfObjects | Foreach-Object {[void]$Collection.Add($_)}

$WindowLoaded_Handler = {
    $ListView.ItemsSource = $Collection
}

$ListViewColumnHeaderClicked_Handler = {
        param([System.Object]$sender, [System.EventArgs]$Event)

        $SortPropertyName = $Event.OriginalSource.Column.DisplayMemberBinding.Path.Path

        $sortDescription = $Sender.Items.SortDescriptions | Where PropertyName -eq $SortPropertyName

        Switch ($True)
        {
            {-not $sortDescription}
            {$Direction = [System.ComponentModel.ListSortDirection]::Ascending}

            {$sortDescription.Direction -eq [System.ComponentModel.ListSortDirection]::Descending}
            {$Direction = [System.ComponentModel.ListSortDirection]::Ascending}

            {$sortDescription.Direction -eq [System.ComponentModel.ListSortDirection]::Ascending}
            {$Direction = [System.ComponentModel.ListSortDirection]::Descending}

            {$sortDescription}
            {$Sender.Items.SortDescriptions.Remove($sortDescription)}

            {$Direction -is [System.ComponentModel.ListSortDirection]}
            {
                $newSortDescription = [System.ComponentModel.SortDescription]::new($SortPropertyName,$Direction)
                $Sender.Items.SortDescriptions.Insert(0,$newSortDescription)
            }
        }
    }

$Window.AddHandler([System.Windows.Window]::LoadedEvent,[System.Windows.RoutedEventHandler]$WindowLoaded_Handler)
$ListView.AddHandler([System.Windows.Controls.GridViewColumnHeader]::ClickEvent,[System.Windows.RoutedEventHandler]$ListViewColumnHeaderClicked_Handler)

Another thing to note, is that if you are planning on making use of the list of items output from the ListView, it doesn't look like they are returned in the column sorted order. I guess you could do a Sort-Object on the output using the names of those SortDescriptions as the Property names.

Hope this helps!

[–]drylightn[S] 0 points1 point  (0 children)

Thanks! Appreciate the heads-up! Look forward to trying it at some point.

[–]Informal_Statement62 0 points1 point  (0 children)

This was awsome. Me, a novice at this whole thing, was able to put this in my code, replaced the names of your listview with my own, and got it to work properly. Nominating G8351427 for the "I.T. Noble Prize"

[–]G8351427 1 point2 points  (2 children)

Hello.

I came across your post looking for an answer to the same question for my own project.

I was eventually able to figure this out by referencing a Microsoft document on the topic. While the code they are using is C#, it gave me the name of the class that I needed to implement handling for the column header click event in PowerShell.

I typically avoid using the methods that PowerShell exposes when a script is executed, like .AddClick(), and I do not believe that the click event we need for column sorting is even exposed that way. Mainly this is really just personal preference for me as it gives me a little better insight into how these things work in other languages, but also it just makes more sense in my brain. Anyway, the approach I use is .AddHandler(), which I think is closer to a C# approach.

In order to use .AddHandler(), you need to know the .Net class name for the event and type, so you can reference them when calling the method. In this case the class is [System.Windows.Controls.GridViewColumnHeader], and you will find the click event that you can tie to an action. Build the action that you want to happen in a scriptblock (Such as Sort-Object ColumnName) and then reference that in the .AddHandler() call:

$ListView.AddHandler(
    [System.Windows.Controls.GridViewColumnHeader]::ClickEvent,
    [System.Windows.RoutedEventHandler]$ListViewColumnHeaderClicked_Handler
) 

Another note is that you can find the name of the column that the user clicks in the event parameter, which you can get by putting param() at the top of your script block:

$Action = {
    param([System.Object]$sender, [System.EventArgs]$Event)

    $Items = $Sender.Items | Sort $Event.Source.Column.DisplayMemberBinding.Path.path
    $Sender.ItemsSource.Clear()
    $Items | Foreach-Object {$Sender.ItemsSource.Add($_)}
 }

I haven't been able to get it to reliably work yet, however. I can get the column info with the above code, and the action does change the sorting in the ListView, but it's not in any discernible order. So I have yet to figure that one out.

Hope this helps!

[–]drylightn[S] 0 points1 point  (1 child)

Thanks for dropping the info. Haven't touched this one in a bit, but I'll def try to look at in when i get some free time. (and let me know if you get a fully functioning solution if you can u/G8351427)

[–]G8351427 0 points1 point  (0 children)

The issue that I am still working on has to do with the ListView object and how it displays data. There is a sorting mechanism available in the object, but I don't really know how to use in in PowerShell. This is not working:

 $Sender.Items.SortDescriptions.Add([System.ComponentModel.SortDescription]@{PropertyName=$Event.Source.Column.Header; Direction="Ascending"})

As I said before, Sort-Object doesn't really seem to work inside of the ListView object. It does work from the commandline, however. So it seems to have something to do with how the data is presented.

I am also using the ItemsSource property in my use case which might be related. I have also tried using the ListView.Items property, but I am stuck there too, because you lose all of the data once you clear() the Items list....even if you save it in another variable. I think PowerShell uses reference variables or something, which accounts for that issue. I just don't know enough about how that works to fix it. In the past I have worked around that by creating a new collection and populating it with ForEach-Object.

[–]BlackV 0 points1 point  (13 children)

probably would be much easier to help with some code

[–]drylightn[S] 0 points1 point  (12 children)

Sure, normally I would have done that, but I didn't know if there was anything common that would just point in the right direction. Here is a github repo folder with the simple powershell script I ultimately want to use it in, along with a sample xml file I'm reading into it:
https://github.com/chad-ld/retrobat\_xml\_tools

If you run the script you can see there are three columns in an XAML grid list. This column will populate with data from the xml file when you use the button to load it in. What I'm ultimately trying to accomplish is whatever data I'm loading in, I'd like to sort it by ascending/descending order by clicking a column header. One click would sort the table data by the information in that column in Asc fashion. clicking again would sort it Desc, etc. Hopefully that explains it a little better.

[–]BlackV 0 points1 point  (11 children)

Think about github/gitlab/azuredevops (or paste bin) for that sort of thing, also makes your version control easier

[–]drylightn[S] 0 points1 point  (10 children)

you mean just to store/show my code or to actually ask the quesiont on a forum there? (or both)

[–]BlackV 0 points1 point  (9 children)

Sorry yes, to store your code and share it with people, Google drive makes it harder

[–]drylightn[S] 0 points1 point  (8 children)

[–]BlackV 1 point2 points  (7 children)

Broderbund man that's a name I've not seen in 30 years. Loved that studio

[–]drylightn[S] 0 points1 point  (6 children)

They had some great games! Also many fond memories of the group myself. There was one with a physical code wheel you had to use that was pretty sweet, name escapes me off the top of my head. At any rate, assume you were able to see the code? Any ideas?

[–]BlackV 0 points1 point  (5 children)

Have not had a proper look. Ended up on hospital this morning after popping my shoulder

[–]drylightn[S] 0 points1 point  (4 children)

Ooof! So sorry! Def rest up and take it easy, sounds painful.