Finally got it after pounding away at this for a couple hours.
Ok here we go:-
vbnet Code:
  1. Imports System.Runtime.InteropServices
  2.  
  3. 'typedef struct tagTVITEMEX {
  4. '  UINT      mask;
  5. '  HTREEITEM hItem;
  6. '  UINT      state;
  7. '  UINT      stateMask;
  8. '  LPTSTR    pszText;
  9. '  int       cchTextMax;
  10. '  int       iImage;
  11. '  int       iSelectedImage;
  12. '  int       cChildren;
  13. '  LPARAM    lParam;
  14. '  int       iIntegral;
  15. '#if (_WIN32_IE >= 0x0600)
  16. '  UINT      uStateEx;
  17. '  HWND      hwnd;
  18. '  int       iExpandedImage;
  19. '#End If
  20. '#If (NTDDI_VERSION >= NTDDI_WIN7) Then
  21. '  int       iReserved;
  22. '#End If
  23. '} TVITEMEX, *LPTVITEMEX;
  24. <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _
  25. Public Structure TVITEMEX
  26.     Public mask As UInteger
  27.     Public hItem As IntPtr
  28.     Public state As UInteger
  29.     Public stateMask As UInteger
  30.     Public pszText As String
  31.     Public cchTextMax As Integer
  32.     Public iImage As Integer
  33.     Public iSelectedImage As Integer
  34.     Public cChildren As Integer
  35.     Public lParam As Integer
  36.     Public iIntegral As Integer
  37.     Public uStateEx As UInteger
  38.     Public hwnd As IntPtr
  39.     Public iExpandedImage As Integer
  40.  
  41.     'Not sure if this should be defined
  42.     'What the flying ***** is NTDDI_WIN7 anyways ?
  43.     Public iReserved As Integer
  44. End Structure
  45.  
  46. 'typedef struct {
  47. '  HTREEITEM hParent;
  48. '  HTREEITEM hInsertAfter;
  49. '#if (_WIN32_IE >= 0x0400)
  50. '  union {
  51. '    TVITEMEX itemex;
  52. '    TVITEM   item;
  53. '  } DUMMYUNIONNAME;
  54. '#Else
  55. '  TVITEM    item;
  56. '#End If
  57. '} TVINSERTSTRUCT, *LPTVINSERTST
  58. <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _
  59. Public Structure TVInsertStruct
  60.     Public hParent As IntPtr
  61.     Public hInsertAfter As IntPtr
  62.  
  63.     <MarshalAs(UnmanagedType.Struct)> _
  64.     Public itemEx As TVITEMEX
  65.  
  66. End Structure
  67.  
  68.  
  69. Public Class TreeViewEx
  70.     Inherits TreeView
  71.  
  72.     Private Const TVM_GETITEMW = (&H1100 + 62)
  73.     Private Const TVM_SETITEMW = (&H1100 + 63)
  74.  
  75.     'Use:-
  76.     'ins = Marshal.PtrToStructure(m.LParam, GetType(TVInsertStruct))
  77.     'to get the Structure used to insert the item
  78.     Private Const TVM_INSERTITEMW = (&H1100 + 50)
  79.  
  80.     Private Const TVM_DELETEITEM = (&H1100 + 1)
  81.     Private Const TVIF_HANDLE = (&H10)
  82.  
  83.     Private g_lstRemNodes As New List(Of TreeNode)
  84.  
  85.     Public Event NodeAdded As EventHandler(Of NodeAddedEventArgs)
  86.     Public Event NodesRemoved As EventHandler(Of NodesRemovedEventArgs)
  87.  
  88.     Protected Overridable Sub OnNodeAdded(ByVal e As NodeAddedEventArgs)
  89.         RaiseEvent NodeAdded(Me, e)
  90.     End Sub
  91.  
  92.     Protected Overridable Sub OnNodesRemoved(ByVal e As NodesRemovedEventArgs)
  93.         RaiseEvent NodesRemoved(Me, e)
  94.     End Sub
  95.  
  96.     Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
  97.         MyBase.WndProc(m)
  98.  
  99.         Dim it As TVITEMEX
  100.  
  101.         'I've only observed this message being sent
  102.         'presumably to select several nodes(1 message per node)
  103.         'before deleting them
  104.         If m.Msg = TVM_GETITEMW Then
  105.             it = Marshal.PtrToStructure(m.LParam, GetType(TVITEMEX))
  106.  
  107.             If it.hItem <> IntPtr.Zero Then
  108.                 Dim n As TreeNode = FindNodeByHandle(it.hItem)
  109.                 g_lstRemNodes.Add(n)
  110.             End If
  111.  
  112.         End If
  113.  
  114.         'GETITEM messages sent before would have identified the nodes
  115.         'that are going to be deleted.
  116.         'IMPORTANT!!!!!
  117.         'I cannot be certain that there are not other circumstances where
  118.         'GETITEM messages would be sent. If this happens then there probably
  119.         'will be nodes in the list that were not actually deleted.
  120.         If m.Msg = TVM_DELETEITEM Then
  121.             OnNodesRemoved(New NodesRemovedEventArgs(g_lstRemNodes.ToArray))
  122.             g_lstRemNodes.Clear()
  123.         End If
  124.  
  125.         If m.Msg = TVM_SETITEMW Then
  126.             it = Marshal.PtrToStructure(m.LParam, GetType(TVITEMEX))
  127.  
  128.             'The handle of a TreeNode object is ReadOnly so
  129.             'there is no way that .Net code can assign a handle
  130.             'to a TreeNode object. Only the TreeView's low level
  131.             'code does therefor when it happens it could only
  132.             'mean that a node was just added to the treeview
  133.             If it.mask And TVIF_HANDLE Then
  134.                 If it.hItem <> IntPtr.Zero Then
  135.                     OnNodeAdded(New NodeAddedEventArgs(FindNodeByHandle(it.hItem)))
  136.                 End If
  137.             End If
  138.         End If
  139.     End Sub
  140.  
  141.     Private Function FindNodeByHandle(ByVal handle As IntPtr) As TreeNode
  142.  
  143.         For Each N As TreeNode In GetAllTreeNodes(Me.Nodes)
  144.             If N.Handle = handle Then Return N
  145.         Next
  146.  
  147.         Return Nothing
  148.     End Function
  149.  
  150.     Private Function GetAllTreeNodes(ByVal baseCollection As TreeNodeCollection) As TreeNode()
  151.         Dim al As New List(Of TreeNode)(100)
  152.  
  153.         For Each N As TreeNode In baseCollection
  154.             al.Add(N)
  155.             If N.Nodes.Count > 0 Then al.AddRange(GetAllTreeNodes(N.Nodes))
  156.         Next
  157.  
  158.         Return al.ToArray
  159.     End Function
  160.  
  161. End Class
  162.  
  163.  
  164. Public Class NodeAddedEventArgs
  165.     Inherits EventArgs
  166.  
  167.     Private _node As TreeNode
  168.  
  169.     Public Sub New(ByVal n As TreeNode)
  170.         _node = n
  171.     End Sub
  172.     Public ReadOnly Property Node() As TreeNode
  173.         Get
  174.             Return _node
  175.         End Get
  176.     End Property
  177. End Class
  178.  
  179. Public Class NodesRemovedEventArgs
  180.     Inherits EventArgs
  181.  
  182.     Private _RemovedNodes As TreeNode()
  183.     Private _cancel As Boolean = False
  184.  
  185.     Public Sub New(ByVal nodes As TreeNode())
  186.         _RemovedNodes = nodes
  187.     End Sub
  188.  
  189.     Public ReadOnly Property RemovedNodes() As TreeNode()
  190.         Get
  191.             Return _RemovedNodes
  192.         End Get
  193.     End Property
  194.  
  195.  
  196. End Class
I've altered the NodeAdded and NodesRemoved events from my earlier example to both return references to EventArgs parameters that reference the node or nodes affected. And I've also resolved those magic numbers to the actual constant names defined by MS and oh boy, hunting them wasnt easy. Anyways enjoy