[Closed] dotNet Treeview TreeViewNodeSorter Property
Hi,
I’ve been trying to figure out how to sort a dotNet treeview by imageIndex, and then alphabetically for each imageIndex.
At the moment I’m removing the nodes, running a qsort on an array, then adding the nodes back onto the tree. Unfortunately this is becoming quite slow and causing some redraw flashes etc.
I’ve been scratching my head on this one for a while now. So far I’ve worked out that I need to use:
dotNetClass "System.Collections.IComparer"
This handy post shows how to build a custom sort for a listview: http://forums.cgsociety.org/showpost.php?p=6001914&postcount=349 But I don’t even know where to begin with recompiling dotNet etc.
Also the MSDN website has some handy info on sorting a treeview, but I have no idea how to convert that code into language that Maxscript understands. http://msdn.microsoft.com/en-us/library/system.windows.forms.treeview.treeviewnodesorter.aspx
I’ve got good maxscript knowledge, but the dotNet side is relatively new to me. Any help will be very much appreciated!
Thanks
Tim
Tim,
Everything you need to know including a great example can be found here:
C:\MAXroot\stdplugs\stdscripts\NET_ListViewWrapper.ms
Look for the lines:
view = dotNetClass “System.Windows.Forms.View”
lv.View = view.Details;
sortOrder = dotNetClass “System.Windows.Forms.SortOrder”
lv.Sorting = sortOrder.Ascending;
Mike
Thanks Mike,
I was actually looking through those files last night.
The example you provided is for a listview, and I can’t get it working with a treeview. It errors out with the message:
– Unable to convert: dotNetObject:System.Windows.Forms.SortOrder to type: System.Collections.IComparer
Also the sortOrder will only allow Ascending, Decending and None – I’m not sure you can tell it to sort by imageIndex. http://msdn.microsoft.com/en-us/library/dscy145f.aspx
Either that or I’m missing something?
Tim
Doh. Why did I send you list view info when I meant to send tree view. WTF?!
I meant “C:\maxroot\stdplugs\stdscripts\NET_TreeViewWrapper.ms” around line: #152
sort alphabetically:
http://msdn.microsoft.com/en-us/library/system.windows.forms.treeview.sorted.aspx
or custom sort:
http://msdn.microsoft.com/en-us/library/system.windows.forms.treeview.treeviewnodesorter.aspx
memo to Mike. Must not try and do 5 things at the same time, you will fail.
fn CompileNodeSorterClass forceRecompile:false =
(
if forceRecompile OR dotnetclass "TreeViewTools.NodeSorter" == undefined do
(
source = ""
append source "using System;
"
append source "using System.Collections;
"
append source "using System.Windows.Forms;
"
append source "namespace TreeViewTools
"
append source "{
" -- open namespace TreeViewTools
append source "public class NodeSorter : IComparer
"
append source "{
" -- open class
append source "public int Compare(object x, object y)
"
append source "{
" -- open method
append source "TreeNode tx = x as TreeNode;
"
append source "TreeNode ty = y as TreeNode;
"
append source "int i = string.Compare(tx.Text, ty.Text);
"
append source "if (i == 0) return string.Compare(tx.Text, ty.Text);
"
append source "return i;
"
append source "}
" -- end method
append source "}
" -- end class
append source "}
" -- end namespace TreeViewTools
csharpProvider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"
compilerParams.ReferencedAssemblies.Add "System.Windows.Forms.dll"
compilerParams.GenerateInMemory = on
compilerResults = csharpProvider.CompileAssemblyFromSource compilerParams #(source)
)
dotnetclass "TreeViewTools.NodeSorter"
)
that function will compile a custom IComparer class “TreeViewTools.NodeSorter” to do what you want.
Then assign an instance of the class to the tree like so:
tv.TreeViewNodeSorter = dotnetobject "TreeViewTools.NodeSorter"
Thanks so much, Gravey! Very much appreciated.
It looks like that code sorts every node alphabetically and not by imageIndex first.
Would I add a line like this before/after the alphabetical sort?:
append source "int i = Compare(tx.imageIndex, ty.imageIndex);
"
Tim
in your case the comparer has to return (tx.imageIndex – ty.imageIndex);
my bad.
As Denis suggested the line
append source "if (i == 0) return string.Compare(tx.Text, ty.Text);
"
should be:
append source "int i = tx.ImageIndex - ty.ImageIndex;
"
NOTE: you may need to restart max after making the change if it appears not to work
If you’re looking for maximum performance, you might want to replace the Sort functionality altogether. The sorting algorithm on the TreeView is quite simple and not too fast.
It’s not too hard to replace it with the QuickSort implemented by .NET’s List collection, much in the same way as you said in your opening post. That can use a generic IComparer<T> too, so you can define how you want the nodes sorted.
For some more details, check this: http://script.threesixty.nl/outliner/index.php/blog/2011/02/refactoring-the-sort-mode
It’s not too technical in there, nor is there code, but just to illustrate the point
Pjanssen, that sounds really interesting. I had a look through the scripts in your (impessive!) outliner tool, but I could not find any code that relates to “quicksort” as you described.
Gravey and denisT, thanks for the help! I’ve written a very quick test rollout to see if it is working, and whenever I evaluate the script I get the error “–Runtime error: Cannot resolve type: TreeviewTools.nodeSorter”
When I evaluate the line “dotnetClass “TreeViewTools.NodeSorter” it comes up as undefined.
Any ideas what is going wrong? Again, many thanks on the help
--Sort Function
fn CompileNodeSorterClass forceRecompile:false =
(
if forceRecompile OR dotnetclass "TreeViewTools.NodeSorter" == undefined do
(
source = ""
append source "using System;
"
append source "using System.Collections;
"
append source "using System.Windows.Forms;
"
append source "namespace TreeViewTools
"
append source "{
" -- open namespace TreeViewTools
append source "public class NodeSorter : IComparer
"
append source "{
" -- open class
append source "public int Compare(object x, object y)
"
append source "{
" -- open method
append source "TreeNode tx = x as TreeNode;
"
append source "TreeNode ty = y as TreeNode;
"
append source "int i = string.Compare(tx.Text, ty.Text);
"
--append source "if (i == 0) return string.Compare(tx.Text, ty.Text);
"
append source "int i = tx.ImageIndex - ty.ImageIndex;
"
append source "return i;
"
append source "}
" -- end method
append source "}
" -- end class
append source "}
" -- end namespace TreeViewTools
csharpProvider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"
compilerParams.ReferencedAssemblies.Add "System.Windows.Forms.dll"
compilerParams.GenerateInMemory = on
compilerResults = csharpProvider.CompileAssemblyFromSource compilerParams #(source)
)
dotnetClass "TreeViewTools.NodeSorter"
)
--Rollout
rollout test "Test Rollout" (
dotNetControl tv "Treeview" width:180 height:190
on test open do (
tv.sorted = true
CompileNodeSorterClass()
tv.TreeViewNodeSorter = dotnetObject "TreeViewTools.NodeSorter"
tv.sort()
)
)
createDialog test 200 200
newNode = test.tv.nodes.add "a1"
newNode.name = "a1"
newNode.imageIndex = 1
newNode = test.tv.nodes.add "a2"
newNode.name = "a2"
newNode.imageIndex = 1
newNode = test.tv.nodes.add "b1"
newNode.name = "b1"
newNode.imageIndex = 0
newNode = test.tv.nodes.add "b2"
newNode.name = "b2"
newNode.imageIndex = 0
It’s not in the current release. And you’d have to pick apart the dll to find it
I hope this doesn’t go too much offtopic, but here’s what I do now, in a class extending TreeView:
private IComparer<TreeNode> _nodeSorter;
public IComparer<TreeNode> NodeSorter
{
get { return _nodeSorter; }
set
{
_nodeSorter = value;
this.Sort();
}
}
public void Sort(TreeNodeCollection nodes, Boolean recursive)
{
if (this.NodeSorter == null || nodes == null)
return;
if (nodes.Count == 1 && recursive)
this.Sort(nodes[0].Nodes, true);
else
{
TreeNode[] dest = new TreeNode[nodes.Count];
nodes.CopyTo(dest, 0);
Array.Sort<TreeNode>(dest, this.NodeSorter);
if (recursive)
{
foreach (TreeNode tn in dest)
{
if (tn.GetNodeCount(false) > 0)
this.Sort(tn.Nodes, true);
}
}
nodes.Clear();
nodes.AddRange(dest);
}
}
So what this does is quite similar to what the TreeView does under the hood, but it uses the Array.Sort method to sort, which is much faster.
You’d have to make sure the default sorting doesn’t kick in though. It’s a bit fiddly to do that, since you’ll have to tell the TreeView that it is not sorted:
new public IComparer TreeViewNodeSorter
{
get { return null; }
set { }
}
new public Boolean Sorted
{
get { return false; }
set { }
}
new public void Sort()
{
this.Sort(this.Nodes, true);
}
Btw, unless you do something that takes only a few lines of .net code, I don’t really see the point of dynamically creating a .net assembly. Why not get visual studio and do it in there? It will give you a great IDE.