Eto Forms: Sorting GridView from column heading click?

I can’t seem to find any documentation on how to sort a gridview on an eto form, by clicking on the column header? Is this possible? I can reorder the columns, but I can’t get them to sort. Here is the example I am working with, (taken from Cell color in Eto with Python)


This code is being run from a ghpython component. It’s just for testing at the moment to learn a little more about eto forms. Any suggestions on sorting the contents would be appreciated!
Eto_Grid.gh (3.4 KB)

I really don’t know, but probably @curtisw will be able to help.

Hi @chanley,

The GridView control has a ColumnHeaderClick event that you will want to handle. In the handler, you’d figure out what column was clicked and then sort your grid’s data store accordingly.

Does this help?

– Dale

@dale, yes, that helps! I was able to get it started. First, (as you suggested) was to add the event handler

self.grid.ColumnHeaderClick += self.OnColumnHeaderClick

Then used python’s sorted function , (with itemgetter), to handle the data sorting:

GridData2 = sorted(GridData, key=itemgetter(sortItem))

Lastly, the code for the event handler, (which seems a bit longwinded).

def OnColumnHeaderClick(self, sender, e):
    GridData = [list(i) for i in zip(Names, Ids, Memory)]
    if e.Column.HeaderText == "Service\t":
        sortItem = 0
        GridData2 = sorted(GridData, key=itemgetter(sortItem))
        self.grid.DataStore = GridData2
    elif e.Column.HeaderText == "PID\t":
        sortItem = 1
        GridData2 = sorted(GridData, key=itemgetter(sortItem))
        self.grid.DataStore = GridData2
    elif e.Column.HeaderText == "Memory\t\t":
        sortItem = 2
        GridData2 = sorted(GridData, key=itemgetter(sortItem))
        self.grid.DataStore = GridData2
    else:
        pass

I had originally thought that there was a sortorder property, but I think that was for windows forms?

The last thing I need to do is to make it sort ascending and descending based on click, (first click == ascending, second click == descending). Thanks again for pointing me in the right direction, and any suggestions are always welcome.

sample file attached.Eto_Grid.gh (7.3 KB)

1 Like

To simplify the OnHeaderClick, you can do this:

sortItem = self.grid.Columns.IndexOf(e.Column)

instead of all the if statements.

2 Likes

Thanks @Tim_Williams ! Much cleaner.

It might be imported to mention, that the columns need to be set to Sortable = true.

https://github.com/picoe/Eto/issues/894

Otherwise the event will not fire.

Kindest
Christian

Hey Chris, How did you solve the first click and second click?

thanks,
Hanan

I haven’t looked at this in quite awhile, (thank god for searchable file names!). Please note that this is quite an old thread, and I never had a need to take this much farther than a learning exercise. I’m certain there are more efficient ways to do this.

From what I can tell, I ended up hacking together a combination of a simple increment function that I put into the onColumnHeaderClick function, using the advice given above, and some python methods to store and sort the data based on a key (the index of the column heading.).

Again, this is quite old, but it seems to still work.

import Eto.Forms as forms
import Eto.Drawing as drawing
import Rhino.UI
from System.Diagnostics import Process
from operator import itemgetter
from System.Windows.Forms import MessageBox

Names = []
Ids = []
SessIds = []
Memory = []

COUNT = 0

#get some data to use in the grid, could be anything
def GetProcs():
    procs = Process.GetProcesses()
    for p in procs:
        Names.append(p.ProcessName)
        Ids.append(p.Id)
        SessIds.append(p.SessionId)
        Memory.append(int(p.WorkingSet / 1024))

#make an incrementer function to use for tracking clicks. (c
def Increment():
    global COUNT
    COUNT = COUNT+1
    return COUNT

class ServicesDialog(forms.Dialog):
    def __init__(self):
        self.Title = "Running Services"
        self.Size = drawing.Size(375,565)
        self.Resizable = True
        
        self.grid = forms.GridView()
        self.grid.Size = drawing.Size(300,400)
        self.grid.AllowColumnReordering = True
        self.grid.CellFormatting += self.OnCellFormatting
        #attach event handler to ColumnHeaderClick event
        self.grid.ColumnHeaderClick += self.OnColumnHeaderClick
        
        #COLUMNS
        serviceColumn = forms.GridColumn()
        serviceColumn.HeaderText = "Service\t"
        serviceColumn.DataCell = forms.TextBoxCell(0)
        serviceColumn.Sortable = True
        
        idsColumn = forms.GridColumn()
        idsColumn.HeaderText = "PID\t"
        idsColumn.DataCell = forms.TextBoxCell(1)
        idsColumn.Sortable = True
        
        memoryColumn = forms.GridColumn()
        memoryColumn.HeaderText = "Memory\t K \t"
        memoryColumn.DataCell = forms.TextBoxCell(2)
        memoryColumn.Sortable = True
        
        self.grid.Columns.Add(serviceColumn)
        self.grid.Columns.Add(idsColumn)
        self.grid.Columns.Add(memoryColumn)

        #self.grid.DataStore = [['0', 'Test0', 'Value0'], ['1', 'Test1', 'Value0'], ['2', 'Test2', 'Value2']]
        #format data for grid with list comprehension, (list(i) makes it a nested list, could also use "i for i"
        GridData = [list(i) for i in zip(Names, Ids, Memory)]

        #using python function to sort based on key(columnHeading)
        #the first column's data type is a string with upper and lower case first letters, let's sort by name first/default
        GridData2 = sorted(GridData, key=lambda x:x[0].lower())
        self.grid.DataStore = GridData2
        
        #instantiate the layout, let the dynamic layout create the rows for all the data in the grid, bind conent to layout
        layout = forms.DynamicLayout()
        layout.AddRow(self.grid)
        self.Content = layout
        
    #some cell formatting
    def OnCellFormatting(self, sender, e):
        #set the text color
        e.ForegroundColor = drawing.Colors.Black
        #color rows with alternating colors
        if e.Row % 2 == 0:
            e.BackgroundColor = drawing.Colors.White
        else:
            e.BackgroundColor = drawing.Colors.LightGrey
            
   #create event Handler for column header click
    def OnColumnHeaderClick(self, sender, e):
        GridData = [list(i) for i in zip(Names, Ids, Memory)]
        Increment()
        try:
            #get the column heading that was clicked
            sortItem = self.grid.Columns.IndexOf(e.Column)
            sort = COUNT % 2
            #if the Name column is clicked, we need to make sure it sorts properly.  (names that start with upper case vs lower case).
            if sortItem == 0:
                GridData2 = sorted(GridData, key=lambda x:x[0].lower(), reverse=sort)
            #else it's any other column, (numbers), sort it
            else:
                GridData2 = sorted(GridData, key=itemgetter(sortItem), reverse=sort)
            self.grid.DataStore = GridData2
        except:
            pass
   
		
def main():
    GetProcs()
    dialog = ServicesDialog()
    dialog.Location = drawing.Point(115, 200)
    dialog.ShowModal()

if x:
    main()

I am attaching the GH file with two gh python components I used as I was working through this exercise. This is windows only, as it is populating the grid with active windows processes.

Eto_Grid.gh (7.9 KB)

3 Likes