I am trying to create a NodeAdded event for a treeview. Is this possible and if so, where would be a good start point?
Printable View
I am trying to create a NodeAdded event for a treeview. Is this possible and if so, where would be a good start point?
I should probably add that I have looked at creating a custom TreeNodeCollection and overide Add(TreeNode Node), but the TreeView.Nodes is a Read-only property.
My advice would be to inherit the TreeView class and override the WndProc method, then see if there is a particular message or set of messages that correspond to nodes being added and/or removed.
I had this problem in the past and it never occurred to me to override WndProc. Thanks to your suggestion I was able to produce a TreeView that raises events when nodes are added or deleted:-
Note however that as of yet I'm retrieve a reference to the node that was added or deleted, so beware of that limitation. Also note that those magic numbers were obtained by me sort of guessing which messages meant to Add/Delete so there might be a circumstance where it may prove that I was wrong. I did a simple Add/Delete test and it seems to correlate especially 4414 but im not 100% certain so be aware of that also.vbnet Code:
Public Class TreeViewEx Inherits TreeView Public Event NodeAdded As EventHandler Public Event NodeRemoved As EventHandler Protected Overridable Sub OnNodeAdded(ByVal e As EventArgs) RaiseEvent NodeAdded(Me, e) End Sub Protected Overridable Sub OnNodeRemoved(ByVal e As EventArgs) RaiseEvent NodeRemoved(Me, e) End Sub Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message) If m.Msg = 4402 Then OnNodeAdded(New EventArgs) End If 'I had to choose between 4414 and 4353 'the number of 4414 messages correlate with 'the amount of nodes removed so Im 97% certain its 'the message sent to remove nodes. 4353 Fires only once 'no matter how many nodes are removed but its related somehow '----- Niya If m.Msg = 4414 Then OnNodeRemoved(New EventArgs) End If MyBase.WndProc(m) End Sub End Class
Rather than using raw numbers, it's generally preferred to declare a constant that uses the actual message name. If you call ToString on the Message object then, in most cases, it should display the message name, e.g. WM_CLICK for a click message. Once you know what message that you're actually dealing with, then you can look it up on MSDN or elsewhere on the web and see how it can be used. You may well find that the WParam or LParam provide access to the node itself somehow.
Actually in this case m.ToString doesnt reveal a name however about 5 minutes after I posted that code above, I was able to locate their declarations in NativeMethods.cs after finding the actual constant names for the values on an MSDN page. I was quite surprised to know that I actually got the numbers right.
Im currently working on a way to retrieve the node that the Add/Remove operation was performed on. Its just a little tricky....got to work with a structure that contains a union(Yuck!! makes everything more confusing) with some strange _Win32_IE macro or compiler directive around it but once I can figure how to ".NET" that sucker properly, I should be able to retrieve a handle which I can use to search the TreeView's nodes for. I'll post it up as soon as I succeed. Wish me luck.
Luck wished :)
Finally got it after pounding away at this for a couple hours.
Ok here we go:-
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:)vbnet Code:
Imports System.Runtime.InteropServices 'typedef struct tagTVITEMEX { ' UINT mask; ' HTREEITEM hItem; ' UINT state; ' UINT stateMask; ' LPTSTR pszText; ' int cchTextMax; ' int iImage; ' int iSelectedImage; ' int cChildren; ' LPARAM lParam; ' int iIntegral; '#if (_WIN32_IE >= 0x0600) ' UINT uStateEx; ' HWND hwnd; ' int iExpandedImage; '#End If '#If (NTDDI_VERSION >= NTDDI_WIN7) Then ' int iReserved; '#End If '} TVITEMEX, *LPTVITEMEX; <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _ Public Structure TVITEMEX Public mask As UInteger Public hItem As IntPtr Public state As UInteger Public stateMask As UInteger Public pszText As String Public cchTextMax As Integer Public iImage As Integer Public iSelectedImage As Integer Public cChildren As Integer Public lParam As Integer Public iIntegral As Integer Public uStateEx As UInteger Public hwnd As IntPtr Public iExpandedImage As Integer 'Not sure if this should be defined 'What the flying ***** is NTDDI_WIN7 anyways ? Public iReserved As Integer End Structure 'typedef struct { ' HTREEITEM hParent; ' HTREEITEM hInsertAfter; '#if (_WIN32_IE >= 0x0400) ' union { ' TVITEMEX itemex; ' TVITEM item; ' } DUMMYUNIONNAME; '#Else ' TVITEM item; '#End If '} TVINSERTSTRUCT, *LPTVINSERTST <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _ Public Structure TVInsertStruct Public hParent As IntPtr Public hInsertAfter As IntPtr <MarshalAs(UnmanagedType.Struct)> _ Public itemEx As TVITEMEX End Structure Public Class TreeViewEx Inherits TreeView Private Const TVM_GETITEMW = (&H1100 + 62) Private Const TVM_SETITEMW = (&H1100 + 63) 'Use:- 'ins = Marshal.PtrToStructure(m.LParam, GetType(TVInsertStruct)) 'to get the Structure used to insert the item Private Const TVM_INSERTITEMW = (&H1100 + 50) Private Const TVM_DELETEITEM = (&H1100 + 1) Private Const TVIF_HANDLE = (&H10) Private g_lstRemNodes As New List(Of TreeNode) Public Event NodeAdded As EventHandler(Of NodeAddedEventArgs) Public Event NodesRemoved As EventHandler(Of NodesRemovedEventArgs) Protected Overridable Sub OnNodeAdded(ByVal e As NodeAddedEventArgs) RaiseEvent NodeAdded(Me, e) End Sub Protected Overridable Sub OnNodesRemoved(ByVal e As NodesRemovedEventArgs) RaiseEvent NodesRemoved(Me, e) End Sub Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message) MyBase.WndProc(m) Dim it As TVITEMEX 'I've only observed this message being sent 'presumably to select several nodes(1 message per node) 'before deleting them If m.Msg = TVM_GETITEMW Then it = Marshal.PtrToStructure(m.LParam, GetType(TVITEMEX)) If it.hItem <> IntPtr.Zero Then Dim n As TreeNode = FindNodeByHandle(it.hItem) g_lstRemNodes.Add(n) End If End If 'GETITEM messages sent before would have identified the nodes 'that are going to be deleted. 'IMPORTANT!!!!! 'I cannot be certain that there are not other circumstances where 'GETITEM messages would be sent. If this happens then there probably 'will be nodes in the list that were not actually deleted. If m.Msg = TVM_DELETEITEM Then OnNodesRemoved(New NodesRemovedEventArgs(g_lstRemNodes.ToArray)) g_lstRemNodes.Clear() End If If m.Msg = TVM_SETITEMW Then it = Marshal.PtrToStructure(m.LParam, GetType(TVITEMEX)) 'The handle of a TreeNode object is ReadOnly so 'there is no way that .Net code can assign a handle 'to a TreeNode object. Only the TreeView's low level 'code does therefor when it happens it could only 'mean that a node was just added to the treeview If it.mask And TVIF_HANDLE Then If it.hItem <> IntPtr.Zero Then OnNodeAdded(New NodeAddedEventArgs(FindNodeByHandle(it.hItem))) End If End If End If End Sub Private Function FindNodeByHandle(ByVal handle As IntPtr) As TreeNode For Each N As TreeNode In GetAllTreeNodes(Me.Nodes) If N.Handle = handle Then Return N Next Return Nothing End Function Private Function GetAllTreeNodes(ByVal baseCollection As TreeNodeCollection) As TreeNode() Dim al As New List(Of TreeNode)(100) For Each N As TreeNode In baseCollection al.Add(N) If N.Nodes.Count > 0 Then al.AddRange(GetAllTreeNodes(N.Nodes)) Next Return al.ToArray End Function End Class Public Class NodeAddedEventArgs Inherits EventArgs Private _node As TreeNode Public Sub New(ByVal n As TreeNode) _node = n End Sub Public ReadOnly Property Node() As TreeNode Get Return _node End Get End Property End Class Public Class NodesRemovedEventArgs Inherits EventArgs Private _RemovedNodes As TreeNode() Private _cancel As Boolean = False Public Sub New(ByVal nodes As TreeNode()) _RemovedNodes = nodes End Sub Public ReadOnly Property RemovedNodes() As TreeNode() Get Return _RemovedNodes End Get End Property End Class