How to: Subclass a TreeView by Using Native CallbacksĀ
This example shows how to subclass a TreeView control to create a NodeMouseClick event, which is not available in the .NET Compact Framework.
The form displays the subclassed TreeView control and information about a clicked node. It shows the a node's text and the x and y coordinates of where the TreeView control was clicked, which changes as you click in different places on a node.
This example program is described in detail in Subclassing Controls with a Managed Window Procedure.
To subclass a TreeView control to create a NodeMouseClick event
In Microsoft Visual Studio 2005, create a Smart Device Pocket PC project.
Add the
Win32
helper class to your project. This code is available in How to: Use a Helper Class for Platform Invokes.Add the
WinProcHooker
class to your project. This code is available in How to: Use a Class for Hooking Windows Procedures.Add the
TreeViewBonus
class to your project.// Extends the standard TreeView control to add an implementation // of the NodeMouseClick event. public class TreeViewBonus : TreeView { // The original parent of this control. Control prevParent = null; // Creates a new instance of the derived TreeView control public TreeViewBonus() { } // Called when the control's parent is changed. Here we hook into that // parent's WndProc and spy on the WM_NOTIFY message. When the parent // changes, we unhook the old parent's WndProc and hook into the new one. protected override void OnParentChanged(EventArgs e) { // unhook the old parent if (this.prevParent != null) { WndProcHooker.UnhookWndProc(prevParent, Win32.WM_NOTIFY); } // update the previous parent prevParent = this.Parent; // hook up the new parent if (this.Parent != null) { WndProcHooker.HookWndProc(this.Parent, new WndProcHooker.WndProcCallback(this.WM_Notify_Handler), Win32.WM_NOTIFY); } base.OnParentChanged(e); } // Occurs when the user clicks a TreeNode with the mouse. public event TreeNodeMouseClickEventHandler NodeMouseClick; // Occurs when the mouse pointer is over the control and a mouse button is clicked. protected void OnNodeMouseClick(TreeNodeMouseClickEventArgs e) { if (NodeMouseClick != null) NodeMouseClick(this, e); } // The method that gets called when a WM_NOTIFY message is received by the // TreeView's parent. // hwnd - The handle of the window that received the message // msg - The message received // wParam - The wParam arguments for the message // lParam - The lParam arguments for the message // handled - Set to true to indicate that this message was handled // Returns an appropriate return code for the message handled int WM_Notify_Handler( IntPtr hwnd, uint msg, uint wParam, int lParam, ref bool handled) { Win32.NMHDR nmHdr = new Win32.NMHDR(); System.Runtime.InteropServices.Marshal.PtrToStructure((IntPtr)lParam, nmHdr); switch (nmHdr.code) { case Win32.NM_RCLICK: case Win32.NM_CLICK: // get the cursor coordinates on the client Point msgPos = Win32.LParamToPoint((int)Win32.GetMessagePos()); msgPos = this.PointToClient(msgPos); // check to see if the click was on an item Win32.TVHITTESTINFO hti = new Win32.TVHITTESTINFO(); hti.pt.X = msgPos.X; hti.pt.Y = msgPos.Y; int hitem = Win32.SendMessage(this.Handle, Win32.TVM_HITTEST, 0, ref hti); uint htMask = ( Win32.TVHT_ONITEMICON | Win32.TVHT_ONITEMLABEL | Win32.TVHT_ONITEMINDENT | Win32.TVHT_ONITEMBUTTON | Win32.TVHT_ONITEMRIGHT | Win32.TVHT_ONITEMSTATEICON); if ((hti.flags & htMask) != 0) { bool leftButton = (nmHdr.code == Win32.NM_CLICK); RaiseNodeMouseClickEvent(hti.hItem, leftButton ? MouseButtons.Left : MouseButtons.Right, msgPos); return 0; } break; default: break; } return 0; } // Raises the TreeNodeMouseClick event for the TreeNode with the specified handle. // hNode - The handle of the node for which the event is raised // button - The [mouse] buttons that were pressed to raise the event // coords - The [client] cursor coordinates at the time of the event void RaiseNodeMouseClickEvent(IntPtr hNode, MouseButtons button, Point coords) { TreeNode tn = FindTreeNodeFromHandle(this.Nodes, hNode); TreeNodeMouseClickEventArgs e = new TreeNodeMouseClickEventArgs( tn, button, 1, coords.X, coords.Y); OnNodeMouseClick(e); } // Finds a TreeNode in the provided TreeNodeCollection that has the handle specified. // Warning: recursion! // tnc - The TreeNodeCollection to search // handle - The handle of the TreeNode to find in the collection // Returns tThe TreeNode if found; null otherwise TreeNode FindTreeNodeFromHandle(TreeNodeCollection tnc, IntPtr handle) { foreach (TreeNode tn in tnc) { if (tn.Handle == handle) return tn; // we couldn't have clicked on a child of this node if this node // is not expanded! if (tn.IsExpanded) { TreeNode tn2 = FindTreeNodeFromHandle(tn.Nodes, handle); if (tn2 != null) return tn2; } } return null; } }
Add the
TreeNodeMouseClickEventArgs
class to your project.// Provides data for the System.Windows.Forms.TreeView.NodeMouseClick event public class TreeNodeMouseClickEventArgs : MouseEventArgs { // Initializes a new instance of the TreeNodeMouseClickEventArgs class. // node - The node that was clicked // button - One of the System.Windows.Forms.MouseButtons members // clicks - The number of clicks that occurred // x - The x-coordinate where the click occurred // y - The y-coordinate where the click occurred public TreeNodeMouseClickEventArgs(TreeNode node, MouseButtons button, int clicks, int x, int y) : base(button, clicks, x, y, 0) { nodeValue = node; } // // Gets the node that was clicked. // public TreeNode Node { get { return nodeValue; } set { nodeValue = value; } } TreeNode nodeValue; public override string ToString() { return string.Format( "TreeNodeMouseClickEventArgs\r\n\tNode: {0}\r\n\tButton: {1}\r\n\tX: {2}\r\n\tY: {3}", nodeValue.Text, Button.ToString(), X, Y); } }
Declare a form variable named
treeViewB
of typeTreeViewBonus
.private TreeViewBonus treeViewB;
Add the following code to the constructor of the
Form1
class, which should follow the call toInitializeComponent
. This code also adds a range of tree nodes and arranges their hierarchy.InitializeComponent(); this.treeViewB = new TreeViewBonus(); this.treeViewB.NodeMouseClick += new TreeNodeMouseClickEventHandler(this.tv_NodeMouseClicked); this.treeViewB.Location = new System.Drawing.Point(18, 16); this.treeViewB.Size = new System.Drawing.Size(205, 144); this.treeViewB.Name = "treeViewB"; // Set up the tree nodes. System.Windows.Forms.TreeNode treeNode1 = new System.Windows.Forms.TreeNode("Node0"); System.Windows.Forms.TreeNode treeNode2 = new System.Windows.Forms.TreeNode("Node2"); System.Windows.Forms.TreeNode treeNode3 = new System.Windows.Forms.TreeNode("Node3"); System.Windows.Forms.TreeNode treeNode4 = new System.Windows.Forms.TreeNode("Node6"); System.Windows.Forms.TreeNode treeNode5 = new System.Windows.Forms.TreeNode("Node7"); System.Windows.Forms.TreeNode treeNode6 = new System.Windows.Forms.TreeNode("Node8"); System.Windows.Forms.TreeNode treeNode7 = new System.Windows.Forms.TreeNode("Node4"); System.Windows.Forms.TreeNode treeNode8 = new System.Windows.Forms.TreeNode("Node1"); System.Windows.Forms.TreeNode treeNode9 = new System.Windows.Forms.TreeNode("Node5"); System.Windows.Forms.TreeNode treeNode10 = new System.Windows.Forms.TreeNode("Node9"); System.Windows.Forms.TreeNode treeNode11 = new System.Windows.Forms.TreeNode("Node10"); System.Windows.Forms.TreeNode treeNode12 = new System.Windows.Forms.TreeNode("Node11"); treeNode2.Text = "Node2"; treeNode4.Text = "Node6"; treeNode5.Text = "Node7"; treeNode6.Text = "Node8"; treeNode3.Nodes.AddRange(new System.Windows.Forms.TreeNode[] { treeNode4, treeNode5, treeNode6}); treeNode3.Text = "Node3"; treeNode7.Text = "Node4"; treeNode1.Nodes.AddRange(new System.Windows.Forms.TreeNode[] { treeNode2, treeNode3, treeNode7}); treeNode1.Text = "Node0"; treeNode12.Text = "Node11"; treeNode11.Nodes.AddRange(new System.Windows.Forms.TreeNode[] { treeNode12}); treeNode11.Text = "Node10"; treeNode10.Nodes.AddRange(new System.Windows.Forms.TreeNode[] { treeNode11}); treeNode10.Text = "Node9"; treeNode9.Nodes.AddRange(new System.Windows.Forms.TreeNode[] { treeNode10}); treeNode9.Text = "Node5"; treeNode8.Nodes.AddRange(new System.Windows.Forms.TreeNode[] { treeNode9}); treeNode8.Text = "Node1"; this.treeViewB.Nodes.AddRange(new System.Windows.Forms.TreeNode[] { treeNode1, treeNode8}); this.Controls.Add(treeViewB);
Add the delegate and the event-handling method for the derived
NodeMouseClick
event to theForm1
class.// Delegate represents the method that will handle // the NodeMouseClick event of a TreeView. // Parameters: // sender - The source of the event. // e - A TreeNodeMouseClickEventArgs that contains the event data. public delegate void TreeNodeMouseClickEventHandler(object sender, TreeNodeMouseClickEventArgs e); private void tv_NodeMouseClicked(object sender, TreeNodeMouseClickEventArgs e) { // Show the current node and the coordinates // in TreeView control where it was clicked. // This is just some of the information you // can obtain from TreeNodeMouseClickEventArgs. // Use a StringBuilder for efficient // use of device resources. StringBuilder sb = new StringBuilder(); sb.Append(e.Node.Text + " "); sb.Append("X: " + e.X.ToString() + ", "); sb.Append("Y: " + e.Y.ToString()); label1.Text = sb.ToString(); }
Compile and run the application.
See Also
Tasks
How to: Use a Class for Hooking Windows Procedures
How to: Use a Helper Class for Platform Invokes
How to: Subclass a TreeView by Using Native Callbacks
How to: Display a Gradient Fill
Concepts
.NET Compact Framework How-To Topics