Sort a list of custom objects
The .NET List object is a great way to store a collection of related objects; objects can be added and retrieved easily and the List can quickly be converted to an array for NXOpen methods that require an array. The List object even has a .Sort method that works great with native data types (string, integer, double, etc). But, if you have ever wanted to sort a list of NX objects, such as drawing sheets by name, the default .Sort method may error out or give unexpected results. The good news is: there is an overloaded version of the .Sort method that allows you to specify exactly what to sort by and how. This is accomplished by writing a custom function that ranks the objects and telling the .Sort method to use the custom function.
If I handed you a sheet of paper with a list of words or integers on it and told you to sort them, it would be a fairly straightforward task. However, if I handed you a box full of books and told you to sort them, you might want to ask an important question:
Sorted by what?:
- number of pages?
- something else?
The pile of books is like the list of objects; each object may return several values, through properties and/or methods, each of which is perfectly valid to sort by. We can provide more information to the List .Sort method, in the form of a function, that specifies exactly what value to sort on and how to rank those values.
The Custom Comparison Function
Our custom comparison function will take two objects (or structures, or values, etc) as input and must return a value that indicates the ranking of these two objects. When comparing two values, let's call them x and y, there are three possible outcomes:
- x > y, meaning x comes after y in the sort
- x < y, meaning x comes before y in the sort
- x = y, meaning in a sorted list either could come first
Here's how the List.Sort method will interpret the comparison function return value:
- return value > 0: x > y
- return value < 0: x < y
- return value = 0: x = y
Some pseudocode for comparing fictional WidgetObjects (according to the weight property) follows:
Function CompareWidgets(byval x as WidgetObject, byval y as WidgetObject) As Integer Dim xWeight as Double = x.Weight Dim yWeight as Double = y.Weight If xWeight > yWeight Then Return 1 End if If xWeight < yWeight Then Return -1 End if If xWeight = yWeight Then Return 0 End if End Function
Sorting Sheet Names
As a simple example, let's sort all the drawing sheets in the part alphabetically. Open or create a part file with several sheets in it and run the following journal:
Option Strict Off Imports System Imports System.Collections.Generic Imports NXOpen Module Module1 Sub Main() Dim theSession As Session = Session.GetSession() Dim lw As ListingWindow = theSession.ListingWindow lw.Open() If IsNothing(theSession.Parts.Display) Then 'active part required, exit journal lw.WriteLine("Active part required, journal exiting...") lw.Close() Return End If 'create list object to hold sheets in the work part Dim mySheets As New List(Of Drawings.DrawingSheet) 'add each sheet object to the list For Each tempSheet As Drawings.DrawingSheet In theSession.Parts.Work.DrawingSheets mySheets.Add(tempSheet) Next 'list the sheet names in the "as-added" order lw.WriteLine("Original sheet order") For Each tempSheet As Drawings.DrawingSheet In mySheets lw.WriteLine(tempSheet.Name) Next lw.WriteLine("") 'sort list alphabetically by sheet name (according to our function) mySheets.Sort(AddressOf CompareSheetNames) 'list the sorted sheet names lw.WriteLine("Sorted Sheet Order") For Each tempSheet As Drawings.DrawingSheet In mySheets lw.WriteLine(tempSheet.Name) Next lw.WriteLine("") 'reverse the list mySheets.Reverse() 'list the reverse sorted sheet names lw.WriteLine("Reverse Sorted Sheet Order") For Each tempSheet As Drawings.DrawingSheet In mySheets lw.WriteLine(tempSheet.Name) Next End Sub Private Function CompareSheetNames(ByVal x As Drawings.DrawingSheet, ByVal y As Drawings.DrawingSheet) As Integer 'case-insensitive sort Dim myStringComp As StringComparer = StringComparer.CurrentCultureIgnoreCase 'for a case-sensitive sort (A-Z then a-z), change the above option to: 'Dim myStringComp As StringComparer = StringComparer.CurrentCulture Return myStringComp.Compare(x.Name, y.Name) End Function End Module
At first glance, our comparison function doesn't look much like the original example code, but we are using a StringComparer object which really makes life easy for us. Using a StringComparer object has three main advantages in this case:
- the string comparison can be based on the current computer's regional settings
- you can choose a case-sensitive or case-insensitive sort order
- the .Compare method's return value is in the exact form we need
Sort Bodies Based on Bounding Box Volume
For a more complicated example, let's sort all the bodies in the work part based on the volume of the body's bounding box. The bounding box size is not available directly from the body object; we'll have to call another function to determine this information.
'NXJournaling 'June 25, 2013 'compare and sort body objects based on bounding box volume Option Strict Off Imports System Imports System.Collections.Generic Imports NXOpen Imports NXOpen.UF Module Module1 Dim theSession As Session = Session.GetSession() Dim theUFSession As UFSession = UFSession.GetUFSession Sub Main() Dim lw As ListingWindow = theSession.ListingWindow lw.Open() Dim workPart As Part = theSession.Parts.Work Dim theBodies As New List(Of Body) 'gather solid and sheet bodies into a list For Each temp As Body In workPart.Bodies theBodies.Add(temp) Next 'write the list out as it was built lw.WriteLine("Before sort:") lw.WriteLine("Body Tag : Bounding box volume") For Each temp As Body In theBodies lw.WriteLine(temp.Tag.ToString & ": " & BoundingBoxVolume(temp).ToString) Next lw.WriteLine("") 'sort the list with our custom compare function theBodies.Sort(AddressOf CompareByBoundingBoxVolume) 'write out sorted list lw.WriteLine("After sort:") lw.WriteLine("Body Tag : Bounding box volume") For Each temp As Body In theBodies lw.WriteLine(temp.Tag.ToString & ": " & BoundingBoxVolume(temp).ToString) Next lw.WriteLine("") 'reverse the list and write it out again lw.WriteLine("Reverse list") lw.WriteLine("Body Tag : Bounding box volume") theBodies.Reverse() For Each temp As Body In theBodies lw.WriteLine(temp.Tag.ToString & ": " & BoundingBoxVolume(temp).ToString) Next End Sub Public Function BoundingBoxVolume(ByVal tempObj As NXObject) As Double Dim bbox(5) As Double theUFSession.Modl.AskBoundingBox(tempObj.Tag, bbox) Return (bbox(3) - bbox(0)) * (bbox(4) - bbox(1)) * (bbox(5) - bbox(2)) End Function Private Function CompareByBoundingBoxVolume(ByVal x As Body, ByVal y As Body) As Integer Dim volX As Double = BoundingBoxVolume(x) Dim volY As Double = BoundingBoxVolume(y) If volX > volY Then Return 1 End If If volX < volY Then Return -1 End If If volX = volY Then Return 0 End If End Function End Module
In this example, we derive values based on body X and body Y and sort based on the derived values.
The .NET framework makes it fairly easy to sort a list of objects; just tell the List object what value you want to sort on and how to rank the values - .NET will take care of the rest!
Have a question on sorting your list? Do you have a good tip you'd like to pass along? Leave it in the comments section below.