Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • World
  • Users
  • Groups
Skins
  • Light
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
Code Project
CODE PROJECT For Those Who Code
  • Home
  • Articles
  • FAQ
Community
  1. Home
  2. General Programming
  3. C#
  4. Sort TreeView items by different logic

Sort TreeView items by different logic

Scheduled Pinned Locked Moved C#
csharpc++winformsquestion
11 Posts 2 Posters 0 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • G Offline
    G Offline
    Gian
    wrote on last edited by
    #1

    Hello, i'd like to sort the child items of a node of a Windows Forms TreeView control using a function of my own that set if the node is to be listed before or after another. Every node has a logic code that i've stored internally using a derived class of TreeNode and i'd like to sort the item by this one. In MFC there's the possibility of defining a logic compare function, is there anything similar in .NET? Or there's another way to add a new child node of an item and sort all its childs without removing them and reloading ? Thanks, Gianmaria

    H 1 Reply Last reply
    0
    • G Gian

      Hello, i'd like to sort the child items of a node of a Windows Forms TreeView control using a function of my own that set if the node is to be listed before or after another. Every node has a logic code that i've stored internally using a derived class of TreeNode and i'd like to sort the item by this one. In MFC there's the possibility of defining a logic compare function, is there anything similar in .NET? Or there's another way to add a new child node of an item and sort all its childs without removing them and reloading ? Thanks, Gianmaria

      H Offline
      H Offline
      Heath Stewart
      wrote on last edited by
      #2

      In the TreeView class, there is no such property or method for you to provide your own sorting algorithm. The Tree-View common control - which the TreeView class encapsulates - does, however. See the documentation for the TVM_SORTCHILDRENCB message in the Platform SDK. You can fill TVSORTCB struct (which you must create in C#) with data from the parent tree node (use TreeNode.Handle), a callback function (shouldn't need to be pinned using the GCHandle since the SendMessage call would be synchronous), and any other data you want to pass (like an integer value that specifies whether to sort ascending or descending). The struct and callback would look like this:

      [StructLayout(LayoutKind.Sequential)]
      private struct LVSORTCB
      {
      public IntPtr TreeItemHandle; // Use TreeNode.Handle
      public TreeViewSortCallback Callback;
      public IntPtr ExtraData;
      }
       
      public delegate IntPtr TreeViewSortCallback(IntPtr node1, IntPtr node1,
      IntPtr extraData);

      The return Type is an IntPtr because an unmanaged int is processor-dependent (32 bits on a 32-bit proc, 64 bits on a 64-bit proc). This makes it more portable. To call it, you need to P/Invoke SendMessage:

      [DllImport("user32.dll")]
      private static extern IntPtr SendMessage(
      IntPtr hWnd,
      [MarshalAs(UnmanagedType.U4)] int msg,
      IntPtr wParam,
      ref TVSORTCB sort);
       
      public void Sort(TreeNode parent, TreeViewSortCallback callback, bool ascending)
      {
      TVSORTCB sort = new TVSORTCB();
      sort.TreeNodeHandle = parent.Handle;
      sort.Callback = callback;
      sort.ExtraData = new IntPtr(ascending ? 0 : 1); // For exmample

      IntPtr hWnd = treeView1.Handle;
      SendMessage(hWnd, 4373, IntPtr.Zero, ref sort);
      }

      Create a new TreeViewSortCallback delegate that references your function and call such a method. You can use your sorting algorithms in there. If you want to get really fancy, you could encapsulate this all into a derivative TreeView class, perhaps called SortableTreeView or something, and let the caller pass an IComparer implementation. With a good design, you could couple the interface (common throughout the .NET FCL) with the callback (making the callback private since IComparer is the typically sorting implementation in .NET - consistency is important). Of course, you could always use your algorithms and manually move n

      G 1 Reply Last reply
      0
      • H Heath Stewart

        In the TreeView class, there is no such property or method for you to provide your own sorting algorithm. The Tree-View common control - which the TreeView class encapsulates - does, however. See the documentation for the TVM_SORTCHILDRENCB message in the Platform SDK. You can fill TVSORTCB struct (which you must create in C#) with data from the parent tree node (use TreeNode.Handle), a callback function (shouldn't need to be pinned using the GCHandle since the SendMessage call would be synchronous), and any other data you want to pass (like an integer value that specifies whether to sort ascending or descending). The struct and callback would look like this:

        [StructLayout(LayoutKind.Sequential)]
        private struct LVSORTCB
        {
        public IntPtr TreeItemHandle; // Use TreeNode.Handle
        public TreeViewSortCallback Callback;
        public IntPtr ExtraData;
        }
         
        public delegate IntPtr TreeViewSortCallback(IntPtr node1, IntPtr node1,
        IntPtr extraData);

        The return Type is an IntPtr because an unmanaged int is processor-dependent (32 bits on a 32-bit proc, 64 bits on a 64-bit proc). This makes it more portable. To call it, you need to P/Invoke SendMessage:

        [DllImport("user32.dll")]
        private static extern IntPtr SendMessage(
        IntPtr hWnd,
        [MarshalAs(UnmanagedType.U4)] int msg,
        IntPtr wParam,
        ref TVSORTCB sort);
         
        public void Sort(TreeNode parent, TreeViewSortCallback callback, bool ascending)
        {
        TVSORTCB sort = new TVSORTCB();
        sort.TreeNodeHandle = parent.Handle;
        sort.Callback = callback;
        sort.ExtraData = new IntPtr(ascending ? 0 : 1); // For exmample

        IntPtr hWnd = treeView1.Handle;
        SendMessage(hWnd, 4373, IntPtr.Zero, ref sort);
        }

        Create a new TreeViewSortCallback delegate that references your function and call such a method. You can use your sorting algorithms in there. If you want to get really fancy, you could encapsulate this all into a derivative TreeView class, perhaps called SortableTreeView or something, and let the caller pass an IComparer implementation. With a good design, you could couple the interface (common throughout the .NET FCL) with the callback (making the callback private since IComparer is the typically sorting implementation in .NET - consistency is important). Of course, you could always use your algorithms and manually move n

        G Offline
        G Offline
        Gian
        wrote on last edited by
        #3

        thanks for the infos. i've implemented a sort function like this in my TreeView class: public IntPtr Compare(IntPtr node1, IntPtr node2, IntPtr extraData) { TreeNode node_1 = TreeNode.FromHandle(this, node1); TreeNode node_2 = TreeNode.FromHandle(this, node2); IntPtr ret= new IntPtr(0); long prog1 = ((myTreeNode)node_1).item_type; long prog2 = ((myTreeNode)node_2).item_type; if(prog1 < prog2) { ret = new IntPtr(-1); } if(prog1 > prog2) { ret = new IntPtr (1); } return ret; } and call the sort function Sort(parent_node, new TreeViewSortCallBack(Compare), 0); at that point, the Compare function is called but both node1 and node2 are zero valued. is because those values are not the nodes but the ItemData like in MFC?

        H 1 Reply Last reply
        0
        • G Gian

          thanks for the infos. i've implemented a sort function like this in my TreeView class: public IntPtr Compare(IntPtr node1, IntPtr node2, IntPtr extraData) { TreeNode node_1 = TreeNode.FromHandle(this, node1); TreeNode node_2 = TreeNode.FromHandle(this, node2); IntPtr ret= new IntPtr(0); long prog1 = ((myTreeNode)node_1).item_type; long prog2 = ((myTreeNode)node_2).item_type; if(prog1 < prog2) { ret = new IntPtr(-1); } if(prog1 > prog2) { ret = new IntPtr (1); } return ret; } and call the sort function Sort(parent_node, new TreeViewSortCallBack(Compare), 0); at that point, the Compare function is called but both node1 and node2 are zero valued. is because those values are not the nodes but the ItemData like in MFC?

          H Offline
          H Offline
          Heath Stewart
          wrote on last edited by
          #4

          As I mentioned before (and that is also documented for the TVSORTCB struct, which you should read), the first two params are the TVITEM structs. You must declare the TVITEM struct in your code and either use Marshal.PtrToStructure, or better yet declare the delegate as:

          delegate IntPtr CompareFunc(ref TVITEM item1, ref TVITEM item2, IntPtr userData);

          This causes the CLR to marshal the references to the TVITEM structs so you don't have to. You then can get the TreeNodes using the TVITEM.hItem fields (or whatever you call the second field in the TVITEM struct, which is the HTREEITEM (handle to a tree node).

          Microsoft MVP, Visual C# My Articles

          G 1 Reply Last reply
          0
          • H Heath Stewart

            As I mentioned before (and that is also documented for the TVSORTCB struct, which you should read), the first two params are the TVITEM structs. You must declare the TVITEM struct in your code and either use Marshal.PtrToStructure, or better yet declare the delegate as:

            delegate IntPtr CompareFunc(ref TVITEM item1, ref TVITEM item2, IntPtr userData);

            This causes the CLR to marshal the references to the TVITEM structs so you don't have to. You then can get the TreeNodes using the TVITEM.hItem fields (or whatever you call the second field in the TVITEM struct, which is the HTREEITEM (handle to a tree node).

            Microsoft MVP, Visual C# My Articles

            G Offline
            G Offline
            Gian
            wrote on last edited by
            #5

            i've tried in the 2 ways, with ref TVITEM and Marshal but it doesnt work. the first two parameters are always 0 or undefined (using ref TVITEM). i've declared the structure [StructLayout(LayoutKind.Sequential)] public struct TVITEM { public IntPtr mask; public IntPtr hItem; public IntPtr state; public IntPtr stateMask; public IntPtr pszText; public IntPtr cchTextMax; public IntPtr iImage; public IntPtr iSelectedImage; public IntPtr cChildren; public IntPtr lParam; } my second test was with public IntPtr Compare(IntPtr node1,IntPtr node2, IntPtr extraData) { TVITEM item1 = new TVITEM(); Marshal.PtrToStructure(node1, item1); TVITEM item2 = new TVITEM(); Marshal.PtrToStructure(node2, item2); TreeNode node_1 = TreeNode.FromHandle(this, item1.hItem); TreeNode node_2 = TreeNode.FromHandle(this, item2.hItem); but always the first 2 parameters are undefined. I've checked the value of extraData and it's correct, the same value i pass when call the sorting. I cant understand why the handle values of the 2 nodes are not passed to the callback function. However forcing the sorting of the two nodes, the tree updates correcly. But there's no way of testing the values of the 2 nodes. I've created a little test application with only these things, and the behaviour is indentical

            H 1 Reply Last reply
            0
            • G Gian

              i've tried in the 2 ways, with ref TVITEM and Marshal but it doesnt work. the first two parameters are always 0 or undefined (using ref TVITEM). i've declared the structure [StructLayout(LayoutKind.Sequential)] public struct TVITEM { public IntPtr mask; public IntPtr hItem; public IntPtr state; public IntPtr stateMask; public IntPtr pszText; public IntPtr cchTextMax; public IntPtr iImage; public IntPtr iSelectedImage; public IntPtr cChildren; public IntPtr lParam; } my second test was with public IntPtr Compare(IntPtr node1,IntPtr node2, IntPtr extraData) { TVITEM item1 = new TVITEM(); Marshal.PtrToStructure(node1, item1); TVITEM item2 = new TVITEM(); Marshal.PtrToStructure(node2, item2); TreeNode node_1 = TreeNode.FromHandle(this, item1.hItem); TreeNode node_2 = TreeNode.FromHandle(this, item2.hItem); but always the first 2 parameters are undefined. I've checked the value of extraData and it's correct, the same value i pass when call the sorting. I cant understand why the handle values of the 2 nodes are not passed to the callback function. However forcing the sorting of the two nodes, the tree updates correcly. But there's no way of testing the values of the 2 nodes. I've created a little test application with only these things, and the behaviour is indentical

              H Offline
              H Offline
              Heath Stewart
              wrote on last edited by
              #6

              Are you passing a parent node when sending the TVM_SORTCHILDRENCB? This should work.

              Microsoft MVP, Visual C# My Articles

              G 1 Reply Last reply
              0
              • H Heath Stewart

                Are you passing a parent node when sending the TVM_SORTCHILDRENCB? This should work.

                Microsoft MVP, Visual C# My Articles

                G Offline
                G Offline
                Gian
                wrote on last edited by
                #7

                yeah, a node with 2 children ones. i've tested it with a messagebox and i'm sure it's the right node. the function is public void Sort(TreeNode parent, TreeViewSortCallback callback, bool ascending) { TVSORTCB sort = new TVSORTCB(); sort.TreeItemHandle = parent.Handle; sort.Callback = callback; sort.ExtraData = new IntPtr(ascending ? 0 : 1); IntPtr hWnd = this.Handle; SendMessage(hWnd, 4373, IntPtr.Zero, ref sort); } I'm sure that it's the right call because, like i've said, i return -1 in the Compare function, the 2 nodes are inverted. Maybe there's some style of the TreeControl to set or similar?

                H 1 Reply Last reply
                0
                • G Gian

                  yeah, a node with 2 children ones. i've tested it with a messagebox and i'm sure it's the right node. the function is public void Sort(TreeNode parent, TreeViewSortCallback callback, bool ascending) { TVSORTCB sort = new TVSORTCB(); sort.TreeItemHandle = parent.Handle; sort.Callback = callback; sort.ExtraData = new IntPtr(ascending ? 0 : 1); IntPtr hWnd = this.Handle; SendMessage(hWnd, 4373, IntPtr.Zero, ref sort); } I'm sure that it's the right call because, like i've said, i return -1 in the Compare function, the 2 nodes are inverted. Maybe there's some style of the TreeControl to set or similar?

                  H Offline
                  H Offline
                  Heath Stewart
                  wrote on last edited by
                  #8

                  No, there's not style to set. Again, read the documentation for the TVM_SORTCHILDRENCB[^] message and related topics linked in the page. Upon further inspection, however, I noticed that the first two params are actually the lParam values of the TVITEM struct, which - believe it or not - is not set by the TreeNode.Tag property. You would have to override the TreeNodeCollection.Add(TreeNode) method with your derivative class and use new to override the Nodes property for both the TreeView and TreeNode classes to return your TreeNodeCollection derivative (they're not virtual). Your Add would be very similar to the current method, which you can see using .NET Reflector[^]. In fact, since some of those method calls are private or internal, you'll have to use base.Add(TreeNode) to get the index, and then global alloc the Tag to get an IntPtr then set the TVITEM using the TVM_SETITEM message. You could do all this, or just sort the tree as I mentioned before yourself, which the TreeView class does instead of using the TVM_SORTCHILDREN message for the Tree-View common control.

                  Microsoft MVP, Visual C# My Articles

                  G 1 Reply Last reply
                  0
                  • H Heath Stewart

                    No, there's not style to set. Again, read the documentation for the TVM_SORTCHILDRENCB[^] message and related topics linked in the page. Upon further inspection, however, I noticed that the first two params are actually the lParam values of the TVITEM struct, which - believe it or not - is not set by the TreeNode.Tag property. You would have to override the TreeNodeCollection.Add(TreeNode) method with your derivative class and use new to override the Nodes property for both the TreeView and TreeNode classes to return your TreeNodeCollection derivative (they're not virtual). Your Add would be very similar to the current method, which you can see using .NET Reflector[^]. In fact, since some of those method calls are private or internal, you'll have to use base.Add(TreeNode) to get the index, and then global alloc the Tag to get an IntPtr then set the TVITEM using the TVM_SETITEM message. You could do all this, or just sort the tree as I mentioned before yourself, which the TreeView class does instead of using the TVM_SORTCHILDREN message for the Tree-View common control.

                    Microsoft MVP, Visual C# My Articles

                    G Offline
                    G Offline
                    Gian
                    wrote on last edited by
                    #9

                    i've implemented the thing in this way: [StructLayout(LayoutKind.Sequential)] public struct TVITEM { public int mask; public IntPtr hItem; public int state; public int stateMask; public IntPtr pszText; public IntPtr cchTextMax; public int iImage; public int iSelectedImage; public int cChildren; public int lParam; } class myTreeNode : TreeNode { public Int32 ItemType; public const int TV_FIRST = 0x1100 ; public const int TVM_GETITEM = TV_FIRST + 62 ; public const int TVM_SETITEM = TV_FIRST + 63 ; public Int32 item_type { set { this.ItemType = value; TVITEM item = new TVITEM(); SendMessage(this.Handle, TVM_GETITEM, new IntPtr(0), ref item); item.lParam = value; SendMessage(this.Handle, TVM_SETITEM, new IntPtr(0), ref item); } get { return this.ItemType; } } [DllImport("user32.dll")] private static extern IntPtr SendMessage( IntPtr hWnd, [MarshalAs(UnmanagedType.U4)] int msg, IntPtr wParam, ref TVITEM item); after adding the node in the TreeView i set the item_type value with my number, and it automatically set the TVITEM lParam value. the problem is that TVM_GETITEM and TVM_SETITEM messages seem to not work :sigh: the TVITEM is not filled but the first message, and the second doesnt set any values in the node. Is the TVITEM structure correct? i've tried also to declared all its properties to IntPtr but with no improvements.

                    H 1 Reply Last reply
                    0
                    • G Gian

                      i've implemented the thing in this way: [StructLayout(LayoutKind.Sequential)] public struct TVITEM { public int mask; public IntPtr hItem; public int state; public int stateMask; public IntPtr pszText; public IntPtr cchTextMax; public int iImage; public int iSelectedImage; public int cChildren; public int lParam; } class myTreeNode : TreeNode { public Int32 ItemType; public const int TV_FIRST = 0x1100 ; public const int TVM_GETITEM = TV_FIRST + 62 ; public const int TVM_SETITEM = TV_FIRST + 63 ; public Int32 item_type { set { this.ItemType = value; TVITEM item = new TVITEM(); SendMessage(this.Handle, TVM_GETITEM, new IntPtr(0), ref item); item.lParam = value; SendMessage(this.Handle, TVM_SETITEM, new IntPtr(0), ref item); } get { return this.ItemType; } } [DllImport("user32.dll")] private static extern IntPtr SendMessage( IntPtr hWnd, [MarshalAs(UnmanagedType.U4)] int msg, IntPtr wParam, ref TVITEM item); after adding the node in the TreeView i set the item_type value with my number, and it automatically set the TVITEM lParam value. the problem is that TVM_GETITEM and TVM_SETITEM messages seem to not work :sigh: the TVITEM is not filled but the first message, and the second doesnt set any values in the node. Is the TVITEM structure correct? i've tried also to declared all its properties to IntPtr but with no improvements.

                      H Offline
                      H Offline
                      Heath Stewart
                      wrote on last edited by
                      #10

                      There are actually two sets of TVM_GETITEM and TVM_SETITEM message for ASCII and Unicode, if you look at the CommCtrl.h:

                      private const int TV_FIRST = 0x1100;
                      private const int TVM_GETITEMA = TV_FIRST + 12;
                      private const int TVM_GETITEMW = TV_FIRST + 62;
                      private const int TVM_SETITEMA = TV_FIRST + 13;
                      private const int TVM_SETITEMW = TV_FIRST + 63;

                      You need to send the message appropriate to the Windows platform you're running on:

                      int msg = TVM_SETITEMA;
                      if (Environment.OSVersion.Platform == PlatformID.Win32NT ||
                      Environment.OSVersion.Platform == PlatformID.WinCE)
                      msg = TVM_SETITEMW;
                      // ...

                      Microsoft MVP, Visual C# My Articles

                      G 1 Reply Last reply
                      0
                      • H Heath Stewart

                        There are actually two sets of TVM_GETITEM and TVM_SETITEM message for ASCII and Unicode, if you look at the CommCtrl.h:

                        private const int TV_FIRST = 0x1100;
                        private const int TVM_GETITEMA = TV_FIRST + 12;
                        private const int TVM_GETITEMW = TV_FIRST + 62;
                        private const int TVM_SETITEMA = TV_FIRST + 13;
                        private const int TVM_SETITEMW = TV_FIRST + 63;

                        You need to send the message appropriate to the Windows platform you're running on:

                        int msg = TVM_SETITEMA;
                        if (Environment.OSVersion.Platform == PlatformID.Win32NT ||
                        Environment.OSVersion.Platform == PlatformID.WinCE)
                        msg = TVM_SETITEMW;
                        // ...

                        Microsoft MVP, Visual C# My Articles

                        G Offline
                        G Offline
                        Gian
                        wrote on last edited by
                        #11

                        with both the two set of messages there's no retrieval of the TVITEM data and setting of my TVITEM data. It's always 0 all :(( It's hard to think it maybe work ;P

                        1 Reply Last reply
                        0
                        Reply
                        • Reply as topic
                        Log in to reply
                        • Oldest to Newest
                        • Newest to Oldest
                        • Most Votes


                        • Login

                        • Don't have an account? Register

                        • Login or register to search.
                        • First post
                          Last post
                        0
                        • Categories
                        • Recent
                        • Tags
                        • Popular
                        • World
                        • Users
                        • Groups