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.
Overview
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?:
- author?
- date?
- publisher?
- category?
- number of pages?
- color?
- 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.
Conclusion
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.
Comments
LINQ
If you are compiling a full-blown application using Visual Studio, you could always use LINQ. I love it! It allows for lots of one-liners.
You may need to import it:
Imports System.Linq
and then you could call the above sort functions as follows:
mySheets = mySheets.OrderBy(Function(x) x.Name).ToList()
(x acts an object in the list so you can then choose the property you want to sort by)
or
theBodies = theBodies.OrderBy(Function(x) BoudingBoxVolume(x)).ToList()
This will do exactly what you're looking for. The Sort "overload" is definitely nice because it gives you more control, but LINQ can really help out with simple scenarios. There is also OrderByDescending() and the ability to sort by several items in a row (List.OrderBy().ThenBy().ThenBy()) in a similar fashion.
For some reason this doesn't seem to work in a journal, which I'm guessing is because the journal uses .NET Framework 2.0? Not sure.
No Linq in Journals
> For some reason this doesn't seem to work in a journal, which
> I'm guessing is because the journal uses .NET Framework 2.0? Not sure.
No, journals use up-to-date versions of the .NET Framework. For example, NX10 uses Framework 4.5.
The problem is that code in the Journal Editor can only reference a few different assemblies, and System.Linq isn't one of them.
Awwww
That makes sense. It seemed like they would use the latest framework, but I couldn't think of why else it wouldn't be there.
Thanks for the clarification!