[Closed] Sort Item numerically
Hello.
Is there a way to sort Items numerically in a listbox or multilistbox ?
Even i modify the value (by a spinner for example) the item move in the list to respect the ascending order. And stay selected if it possible.
That’s possible in maxscript ? I think yes
Try this one:
rollout roSortTest "Sort Test" width:136 height:246
(
listbox lbx1 "Items:" pos:[4,3] width:127 height:14
button btn1 "add" pos:[11,213] width:54 height:26
button btn2 "sort" pos:[71,213] width:54 height:26
fn compareFn el1 el2 = (
return (el1 as integer) - (el2 as integer)
)
on btn1 pressed do (
local auxArray = lbx1.items
append auxArray ((random 1 100) as string)
lbx1.items = auxArray
)
on btn2 pressed do (
local auxArray = lbx1.items
qsort lbx1.items compareFN
lbx1.items = auxArray
)
)
createDialog roSortTest
Execute it, add some random numbers and then press the sort button.
Since the property items from listbox is an array of strings, if you sort it, will give you different results as it would be expected from an array of integers. For example, the string “10” would be before “2”. Ok, that’s not what you want. So to solve this situation, you can use the qsort function that let you define a custom sort function. That way you can sort the items as integers. This is the method I use in this script.
HalfVector.
Hello halfvector.
Thanks a lot for your trick
I’ll analyse it now.
I’am very bad in maxscript i buy a book about maxscript but i don’t receive it yet 🙁
Could you explain me the role of the function ? I don’t understand how it work.
Thanks very much
The function qsort does a sorting based in the algorithm quick sort. The first argument is the array to be sorted and the second, a callback function that qsort will use internally to sort the array. This callback function will tell qsort how to manage the elements of the array passed as first argument.
So in the script I showed you, we have the compareFn function:
fn compareFn el1 el2 = (
return (el1 as integer) - (el2 as integer)
)
This function must return an integer value less than 0 if el2 is greater than el1, 0 if el1 and el2 are equal and an integer value grater than 0 if el1 is greater than el2. So in this case we can subtract el2 to el1 and we are done. For example, imagine el1 = 5 and el2 = 8. The subtraction will result in 5 – 8 = -3 so as it would be expected, qsort will determine that el2 is greater than el1.
So with qsort you can compare anything you want. For example, say you have a struct like this:
struct Dog (
name,
age
)
and you want to sort an array full of dogs by their age. Now that’s easy with qsort. The “hard part” is define our compare function but as you can see, it’s almost the same as the previous one:
fn sortDogsCB el1 el2 = (
return (el1.age - el2.age)
)
The only thing that changes is that we sort in base to the age of the dog. So we subtract the age property from the dog struct.
I’ve written a little example script that shows the dogs case:
struct Dog (
name,
age
)
fn sortDogsCB el1 el2 = (
return (el1.age - el2.age)
)
dogs = #()
append dogs (Dog name:"tobias" age: 8)
append dogs (Dog name:"cora" age: 3)
append dogs (Dog name:"trabuco" age: 10)
append dogs (Dog name:"loco" age: 1)
format "
UNSORTED LIST
"
for dog in dogs do format "Dog name: % age: %
" dog.name dog.age
qsort dogs sortDogsCB
format "
SORTED LIST
"
for dog in dogs do format "Dog name: % age: %
" dog.name dog.age
If you execute this script, you’ll see that it outputs an unsorted list of dogs and then the same list but this time sorted by age.
Hope qsort makes sense to you now.
HalfVector.
Hello Halfvector.
Thank you for your explanation.
I understand what you do in the script.
Thanks a lot again
Hello Halfvector.
Sorry to disturb you again.
i just have two other questions for you.
Your code work perfectly.
I fact, i have a listbox with a spinner an when i select a item in the listbox i can modify it with the spinner and the items sort automaticaly (where i used your code ).
But (and is normal) when the items sort. The selection stay in the same place.
For example:
I have 3 strings for my listbox (“10”,“20”,“30”) like item[1], item[2], item[3]. i select item[2] (“20”). I modify it with the spinner and type “40”. When i press enter “40” change position like i want. It go after “30” so it pass in position Item[3]. Exactly what i want. But for an ergonomical way. It’ll be better if the selection follow the change of the string. And it don’t do that yet.
I see also that if i use the spinner to drag the value (instead of type the value) when the value is equal an other and become more great. The value i edit go up in the list and if i continue to change the value (without release the mouse’s button) the both value change. Like one value push the other. That’s weird but interesting.
Test it with my code it’s very fun.
Take the “10” and increase it a lot when you come at “30” and continue to increase the three value increase too.
Do you have a way to avoid this case ?
I think that the code must avoid that two items have the same string. So when the Spinner.value approach of an other and come to be the same, the code make it jump the value to one integer more or less by follow the spinner.
I don’t know if i was very clear in my poor english. 🙁
this is my code:
utility listbox "ListboxTest"
(
group "LisBox"
(
label espbet " " height:3
listbox pass "Items :" items:#("10","20","30")
spinner offset "Modify :" range:[0,1000,0] type:#integer
label espbet01 " " height:2
edittext passitemNo "ListItemNo :" readonly:true --This edittext is here just to verified the item number for the selected string.
)--end group
fn compareFn el1 el2 =
(
return (el1 as integer) - (el2 as integer)
)--end fn compareFn
on pass selected LinkFrameNo do
(
offset.value = pass.items[LinkFrameNo] as integer
passitemNo.text = linkFrameNo as string
)--end on pass
on offset changed value do
(
pass.selected = value as string
local auxArray = pass.items
qsort pass.items compareFN
pass.items = auxArray
--select pass.selection
)--end on offset
)--end utility
Do you have a solution for it ?
Thank you very much for your help.
Nice behavior.
Ok, one thing you could do is, every time the value of the spinner changes, store the selected value into a temporal variable and once the list is sorted, find that value in the list and reselect it. Of course, you could get unexpected results when two values are the same. I mean, say you have three values in the list:
10
20
30
If you change 10 to 20, when you try to find “20” (that previously was 10), I can’t guarantee that the “20” being selected is the new “20” (the old “10”) or the other “20”.
Anyway, if your list items are only numbers it should not be a problem. So you could substitute you spinner changed event handler by this one:
on offset changed value do
(
pass.selected = value as string
-- Store the previously selected item, later will be reselected
local selected = pass.selected
local auxArray = pass.items
qsort auxArray compareFN
pass.items = auxArray
-- Find our previously selected item in the list
local newLocation = findItem pass.items selected
-- If exists, select it again
if newLocation != 0 do
pass.selection = newLocation
)--end on offset
HalfVector.
Hello halfvector.
Thank you so much.
That’s pretty cool :buttrock:
Exactly what i want.
I got to analyse your code.
How do you learn maxscript ?
Mainly with the reference that comes with MAX. At first glance is a bit intimidating but then you realize it’s really helpful.
Yes. I begin to learn maxscript three weeks ago.
I already make programs in basic language on automatized machines in industry.
it’s not the same thing but it’s a good help.
I work in a small company and i want to make some tools to automatise some functions of max. I think it’s a good thing for a company that want to evolve.
And you what do you do like job ?
I haven’t got a job right now.
I’m programming a game engine with a friend and that’s the reason I use MAXScript. We need tools to export data (vertices, faces, skin weights, bones animation, etc) from MAX to our engine or link custom atributes to MAX nodes (light special properties, new material properties, etc). So the easiest and fastest way to do that is using MAXScript. It’s really useful.
HalfVector.