ExplorerTree.cs
上传用户:nnpulika
上传日期:2013-02-15
资源大小:597k
文件大小:34k
源码类别:

状态条

开发平台:

C#

  1. using System;
  2. using System.Collections;
  3. using System.ComponentModel;
  4. using System.Drawing;
  5. using System.Windows.Forms;
  6. using System.Runtime.InteropServices;
  7. using System.Diagnostics;
  8. using System.Text;
  9. using UtilityLibrary.Win32;
  10. using UtilityLibrary.General;
  11. namespace UtilityLibrary.WinControls
  12. {
  13. #region Delegates
  14. public delegate void ExplorerNodeChangedHandler(ExplorerTree et, TreeNodeData tnd);
  15. #endregion
  16. #region Enumerations
  17. [Flags]
  18. public enum TreeNodeFlags
  19. {
  20. ShowFiles        = 0x00001,
  21.         ShowHiddenFiles  = 0x00003,
  22. ShowName         = 0x00004,
  23. ShowPath         = 0x00008
  24. }
  25. #endregion
  26. #region Helper Classes
  27. public class ShellIDList : ShellHandle
  28. {
  29. #region Class Variables
  30. IntPtr hWnd = IntPtr.Zero;
  31. #endregion
  32. #region Constructors
  33. public ShellIDList(IntPtr idList, IntPtr hWnd): base(idList) 
  34. {
  35. Debug.Assert(handle != IntPtr.Zero);
  36. this.hWnd = hWnd;
  37. }
  38. public ShellIDList(IntPtr idList) : base(idList)
  39. {
  40. Debug.Assert(idList != IntPtr.Zero);
  41. }
  42.        #endregion
  43. #region Overrides
  44. #endregion
  45. #region Operators
  46. public static implicit operator IntPtr(ShellIDList shellIDList)
  47. {
  48. return shellIDList.Handle;
  49. }
  50. #endregion
  51. #region Properties
  52. public int Length
  53. {
  54. get 
  55. {
  56. // We have a valid handle find its length
  57. int count = 0;
  58. int length = 0;
  59. // Iterate through the list to find every item length
  60. // and add it to the count
  61. int pBase = (int)handle;
  62. do 
  63. {
  64. // Make sure we are not trying to ready zero out memory
  65. if ( !IsValidPointer((IntPtr)pBase))
  66. break;
  67. ITEMIDLIST idl = (ITEMIDLIST)Marshal.PtrToStructure((IntPtr)pBase, 
  68. typeof(ITEMIDLIST));
  69. count = idl.mkid.cb;
  70. pBase = pBase + count; 
  71. length += count;
  72. }
  73. while ( count != 0 );
  74. return length;
  75. }
  76. }
  77. #endregion 
  78. #region Methods
  79. public string GetPath()
  80. {
  81. StringBuilder path = new StringBuilder(1024);
  82. int result = WindowsAPI.SHGetPathFromIDList(handle, path);
  83. if ( result == 0 )
  84. return string.Empty;
  85. return path.ToString();
  86. }
  87. public IntPtr GetFirstChild()
  88. {
  89. return GetNextChild(this.Handle);
  90. }
  91. public IntPtr GetLastChild()
  92. {
  93. return GetLastChild(this.Handle);
  94. }
  95. public int GetIconIndex(ShellFileInfoFlags flags)
  96. {
  97. SHFILEINFO shfi = new SHFILEINFO();
  98.                         
  99. // Get small icon index
  100. ShellFileInfoFlags fileFlags = ShellFileInfoFlags.SHGFI_SYSICONINDEX | ShellFileInfoFlags.SHGFI_PIDL;
  101. fileFlags |= flags;
  102. WindowsAPI.SHGetFileInfo(handle, 0, out shfi, (uint)Marshal.SizeOf(typeof(SHFILEINFO)), fileFlags);
  103. return shfi.iIcon;
  104. }
  105. public int GetCount()
  106. {
  107. // Make sure we are not trying to ready zero out memory
  108. int pBase = (int)handle;
  109. if ( !IsValidPointer((IntPtr)pBase))
  110. return 0;
  111. // Bring the pointer into a managed type 
  112. ITEMIDLIST idList = (ITEMIDLIST)Marshal.PtrToStructure(handle, typeof(ITEMIDLIST));
  113. return idList.mkid.cb;
  114. }
  115. static public IntPtr GetNextChild(IntPtr parent)
  116. {
  117. // Make sure we are not trying to ready zero out memory
  118. if ( !IsValidPointer((IntPtr)parent))
  119. return IntPtr.Zero;
  120. // Bring the pointer into a managed type 
  121. ITEMIDLIST idList = (ITEMIDLIST)Marshal.PtrToStructure(parent, typeof(ITEMIDLIST));
  122. // Get the size of the idList
  123. int cb = idList.mkid.cb;
  124. // If the size is zero, it is the end of the list. 
  125. if ( cb == 0) 
  126. return IntPtr.Zero; 
  127. // Add cb to original list to obtain the next child list 
  128. IntPtr pBase = parent;
  129. IntPtr nextList = (IntPtr)((int)pBase + cb); 
  130. // Make sure we are not trying to ready zero out memory
  131. if ( IsValidPointer(nextList) )
  132. {
  133. // Bring the pointer into a managed type 
  134. idList = (ITEMIDLIST)Marshal.PtrToStructure(nextList, typeof(ITEMIDLIST));
  135. return nextList;
  136. }
  137. else
  138. {
  139. // Return IntPtr.Zero if we reached the terminator, or a pidl otherwise. 
  140. return IntPtr.Zero;
  141. }
  142. }
  143. static public IntPtr GetLastChild(IntPtr parent)
  144. {
  145. IntPtr current = GetNextChild(parent);
  146. IntPtr last = IntPtr.Zero;
  147. while ( current != IntPtr.Zero)
  148. {
  149. last = current;
  150. current = GetNextChild(current);
  151. }
  152. return last;
  153. }
  154. static public ShellIDList Combine(IMalloc iMalloc, ShellIDList idList1, ShellIDList idList2)
  155. {
  156. // Combine PUIDL to form a new one
  157. IntPtr list1 = IntPtr.Zero;
  158. if ( idList1 != null )
  159. list1 = idList1.Handle;
  160. IntPtr list2 = IntPtr.Zero;
  161. if ( idList2 != null )
  162. list2 = idList2.Handle;
  163. // Get PUIDL lengths
  164. int length1 = 0;
  165. if ( idList1 != null )
  166. {
  167. length1 = idList1.Length;
  168. }
  169. int length2 = 0;
  170. if ( idList2 != null )
  171. {
  172. length2 = idList2.Length;
  173. }
  174. // Allocate memory for the combined handle
  175. IntPtr newHandle = IntPtr.Zero;
  176. newHandle = (IntPtr)iMalloc.Alloc((uint)(length1 + length2 + Marshal.SizeOf(typeof(ushort))));
  177. IntPtr pBase = newHandle;
  178. // If memory was successfully allocated
  179. if ( newHandle != IntPtr.Zero )
  180. {
  181. if ( length1 > 0 )
  182. {
  183. for ( int i = 0; i < length1; i++ )
  184. {
  185. byte currentByte = Marshal.ReadByte(list1, i);
  186. Marshal.WriteByte(pBase, currentByte);
  187. pBase = (IntPtr)((int)pBase + 1);
  188. }
  189. }
  190. if ( length2 > 0 )
  191. {
  192. for ( int i = 0; i < length2; i++ )
  193. {
  194. byte currentByte = Marshal.ReadByte(list2, i);
  195. Marshal.WriteByte(pBase, currentByte);
  196. pBase = (IntPtr)((int)pBase + 1);
  197. }
  198. }
  199.                 
  200. // Append terminating zero
  201. Marshal.WriteInt16(pBase, 0);
  202. return new ShellIDList(newHandle);
  203. }
  204. return null;
  205. }
  206. public bool IsRootList()
  207. {
  208. return Length == 0;
  209. }
  210. static bool IsValidPointer(IntPtr pBase)
  211. {
  212. // Make sure we are not trying to read into null memory
  213. if ( pBase == IntPtr.Zero )
  214. return false;
  215. short readValue = Marshal.ReadInt16(pBase);
  216. return readValue != 0;
  217. }
  218. #endregion
  219. #region Implementation
  220. #endregion
  221.     }
  222. public class ShellIFolder : COMInterface
  223. {
  224. #region Class Variables
  225. IShellFolder iShellFolder = null;
  226.     #endregion
  227. #region Constructors
  228. public ShellIFolder(IShellFolder iShellFolder): base((IUnknown)iShellFolder) 
  229. {
  230.             this.iShellFolder = iShellFolder;
  231. }
  232. public ShellIFolder(ShellIFolder shellIFolder) : base((IUnknown)shellIFolder.Interface)
  233. {
  234. IShellFolder iShellFolder = shellIFolder.Interface;
  235. this.iShellFolder = iShellFolder;
  236. }
  237. public static ShellIFolder CreateShellIFolder(ShellIFolder shellIFolder, ShellIDList idList)
  238. {
  239. IShellFolder iShellFolder = shellIFolder.BindToObject(idList);
  240. Debug.Assert(iShellFolder != null);
  241. return new ShellIFolder(iShellFolder);
  242. }
  243. ~ShellIFolder()
  244. {
  245. Dispose(false);
  246. }
  247. #endregion
  248. #region Properties
  249. public IShellFolder Interface
  250. {
  251. get { return iShellFolder; }
  252. }
  253. #endregion
  254. #region Methods
  255. public string GetDisplayNameOf(IntPtr idList, ShellGetDisplayNameOfFlags flags)
  256. {
  257. // Use the native handle this class is wrapping to obtain the Display Name of the node
  258. STRRET strRet = new STRRET();
  259.             iShellFolder.GetDisplayNameOf(idList, flags, out strRet);
  260. if ( strRet.uType == STRRETFlags.STRRET_WSTR )
  261. {
  262. // Get the OLE string into a managed string
  263.                 IntPtr pOLEString = (IntPtr)strRet.pOleStr;
  264.                 string displayName = Marshal.PtrToStringAuto(pOLEString);
  265. // Release memory
  266. WindowsAPI.SHFreeMalloc(pOLEString);
  267.                 
  268. return displayName;
  269. }
  270.                         
  271. return string.Empty;
  272. }
  273. public void GetAttributesOf(uint count, ref IntPtr idList, out GetAttributeOfFlags attributes)
  274. {
  275. iShellFolder.GetAttributesOf(count, ref idList, out attributes);
  276. }
  277. public IShellFolder BindToObject(IntPtr idList)
  278. {
  279. IShellFolder iFolder = null;
  280. REFIID refiid =  new REFIID("000214E6-0000-0000-c000-000000000046");
  281. int result = iShellFolder.BindToObject(idList, IntPtr.Zero, ref refiid, ref iFolder);
  282. Debug.Assert(iFolder !=null);
  283. return iFolder;
  284. }
  285. public IEnumIDList EnumObjects(IntPtr hWnd, ShellEnumFlags flags)
  286. {
  287. IEnumIDList iEnumIDList = null;
  288. iShellFolder.EnumObjects(hWnd, flags, ref iEnumIDList);
  289. // iEnumIDList could be null if we are enumerating a removable media drive
  290. return iEnumIDList;
  291. }
  292. public int CompareIDs(int lparam, ShellIDList shellIDList1, ShellIDList shellIDList2)
  293. {
  294.             IntPtr list1 = shellIDList1.GetLastChild();
  295. IntPtr list2 = shellIDList2.GetLastChild();
  296.             int result = iShellFolder.CompareIDs(lparam, list1, list2);
  297.             
  298. if ( WindowsAPI.FAILED(result) )
  299. return 0;
  300. short ret = WindowsAPI.HRESULT_CODE(result);
  301. if ( ret < 0 ) return -1;
  302. else if ( ret > 0) return 1;
  303.             
  304. return 0;            
  305. }
  306. public int GetUIObjectOf(IntPtr hWnd, uint count, ref IntPtr idList, REFIID riid, ref IUnknown iUnknown)
  307. {
  308. uint arrayInOut = 0;
  309. return iShellFolder.GetUIObjectOf(hWnd, count, ref idList, ref riid,  out arrayInOut, ref iUnknown);
  310. }
  311. #endregion
  312. #region Implementation
  313. #endregion
  314. }
  315. public class ShellIEnumIDList : COMInterface
  316. {
  317. #region Class Variables
  318. IEnumIDList iEnumIDList = null;
  319. #endregion
  320. #region Constructors
  321. public ShellIEnumIDList(IEnumIDList iEnumIDList) : base((IUnknown)iEnumIDList)
  322. {
  323. this.iEnumIDList = iEnumIDList;
  324. }
  325. public static ShellIEnumIDList CreateShellIEnumIDList(IntPtr hWnd, ShellIFolder shellIFolder, ShellEnumFlags flags)
  326. {
  327. // Interface could be null if we are trying to
  328. // enumerate a removable media
  329. IEnumIDList iEnumIDList = shellIFolder.EnumObjects(hWnd, flags);
  330. // Don't let the object to succed if we could get the interface
  331. if ( iEnumIDList == null )
  332. throw new Exception("Failed to obtain IEnumIDList interface.");
  333. return new ShellIEnumIDList(iEnumIDList);
  334. }
  335. #endregion
  336. #region Properties
  337. public IEnumIDList Interface
  338. {
  339. get { return iEnumIDList; }
  340. }
  341. #endregion
  342. #region Methods
  343. public int Next(uint count, ref IntPtr idListPtr, out uint fetched)
  344. {
  345. return iEnumIDList.Next(count, ref idListPtr, out fetched);
  346. }
  347. #endregion
  348. }
  349. [ToolboxItem(false)]
  350. public class ShellPopupMenu : ContextMenu 
  351. {
  352. #region Class Variables
  353. IContextMenu iContextMenu = null;
  354. const int FIRST_COMMAND = 20000;
  355. const int LAST_COMMAND = FIRST_COMMAND + 1000;
  356. int lastCommand = 0;
  357. IntPtr hOwner = IntPtr.Zero;
  358. IntPtr hOldWinProc = IntPtr.Zero;
  359. #endregion
  360. #region Constructors
  361. public ShellPopupMenu()
  362. {
  363. }
  364. public void Initialize(ShellIFolder folder, ShellIDList idList)
  365. {
  366. // Dispose of any resources we have before
  367. Dispose();
  368. // Clear any previous menu items
  369. MenuItems.Clear();
  370. IntPtr childIDList = idList.GetLastChild();
  371. // If there is not child, use the parent
  372. if ( childIDList == IntPtr.Zero )
  373. {
  374. childIDList = idList.Handle;
  375. Debug.Assert(childIDList!=IntPtr.Zero);
  376. }
  377. REFIID riid = new REFIID("000214e4-0000-0000-c000-000000000046");
  378. IUnknown iUnknown = null;
  379. folder.GetUIObjectOf(IntPtr.Zero, 1, ref childIDList, riid, ref iUnknown);
  380. Debug.Assert(iUnknown!=null);
  381. iContextMenu = (IContextMenu)iUnknown;
  382.             
  383. // Get the menu items of the context menu
  384. QueryContextMenu();
  385.        }
  386. ~ShellPopupMenu()
  387. {
  388. Dispose(false);;
  389. }
  390. #endregion
  391. #region Overrides
  392. protected override void Dispose(bool disposing)
  393. {
  394. base.Dispose(disposing);
  395. // Relese previous reference to interface
  396. Release();
  397. }
  398. #endregion
  399. #region Properties
  400. #endregion
  401. #region Methods
  402. public void InvokeCommand(int commandID)
  403. {
  404. if ( iContextMenu != null )
  405. {
  406. CMINVOKECOMMANDINFO ici = new CMINVOKECOMMANDINFO();
  407. ici.cbSize = (uint)Marshal.SizeOf(typeof(CMINVOKECOMMANDINFO));
  408. ici.hwnd = hOwner;
  409. ici.nShow = (int)ShowWindowStyles.SW_SHOWNORMAL;
  410. ici.lpVerb = (IntPtr)WindowsAPI.MAKEINTRESOURCE(commandID - FIRST_COMMAND);
  411. WinErrors error = (WinErrors)iContextMenu.InvokeCommand(ref ici);
  412. }
  413. }
  414. public new void Dispose()
  415. {
  416. // Let the Garbage Collector know that it does
  417. // not need to call finalize for this class
  418. GC.SuppressFinalize(this);
  419. // Do the disposing
  420. Dispose(true);
  421. }
  422. #endregion
  423. #region Implementation
  424. void QueryContextMenu()
  425. {
  426. Debug.Assert(iContextMenu!=null);
  427. QueryContextMenuFlags flags = QueryContextMenuFlags.CMF_NODEFAULT| QueryContextMenuFlags.CMF_EXPLORE;
  428. int hr = iContextMenu.QueryContextMenu(Handle, 0, FIRST_COMMAND, LAST_COMMAND, flags);
  429. Debug.Assert(WindowsAPI.SUCCEEDED(hr));
  430. // Get last command index that the shell assigned us
  431. lastCommand = FIRST_COMMAND + WindowsAPI.HRESULT_CODE(hr) -1;
  432. }
  433. void Release()
  434. {
  435. // Release interface
  436. if ( iContextMenu!=null )
  437. {
  438. IUnknown iUnknown = (IUnknown)iContextMenu;
  439. Debug.Assert(iUnknown!=null);
  440. iUnknown.Release();
  441. iContextMenu = null;
  442. }
  443. }
  444. #endregion
  445.         
  446. }
  447. public class TreeNodeData
  448. {
  449. #region Class Variables
  450. ShellIDList idList = null;
  451. ShellIFolder folder = null;
  452. TreeNodeFlags flags = TreeNodeFlags.ShowName;
  453. IntPtr handle = IntPtr.Zero;
  454. bool expanded = false;
  455. #endregion
  456. #region Constructors
  457. #endregion
  458.                 
  459. #region Properties
  460. public ShellIDList IDList
  461. {
  462. set { idList = value; }
  463. get { return idList; }
  464. }
  465. public ShellIFolder Folder
  466. {
  467. set { folder = value; }
  468. get { return folder; }
  469. }
  470. public TreeNodeFlags Flags
  471. {
  472. set { flags = value; }
  473. get { return flags; }
  474. }
  475. public IntPtr Handle
  476. {
  477. set { handle = value; }
  478. get { return handle; }
  479. }
  480. #endregion
  481. #region Methods
  482. public bool IsValid()
  483. {
  484. return idList != null && folder != null;
  485. }
  486. #endregion
  487. #region Implementation
  488. // Properties
  489. internal bool Expanded 
  490. {
  491. set { expanded = value; }
  492. get { return expanded; }
  493. }
  494. // Functions
  495.         
  496. #endregion
  497. }
  498.     #endregion
  499. /// <summary>
  500. /// Explorer like implementation of a Tree Control that show the resources in the Computer
  501. /// </summary>
  502. [ToolboxItem(false)]
  503. public class ExplorerTree : UtilityLibrary.WinControls.TreeViewEx
  504. {
  505. #region Class Variables
  506. // Property backers
  507. IntPtr hSystemImageList = IntPtr.Zero;
  508. bool optimizeMemoryUsage = false;
  509. TreeNodeFlags treeNodeFlags = TreeNodeFlags.ShowName;
  510. // Keep an instance of the shell memory allocator since we are going
  511. // to use it a lot
  512. IMalloc iMalloc = null;
  513. // Other helpers
  514. ShellIFolder desktopFolder = null;
  515. Hashtable hashTable = new Hashtable();
  516. TreeNode rootTreeNode = null;
  517. ShellPopupMenu contextMenu = new ShellPopupMenu();
  518. #endregion
  519. #region Constructors
  520. public ExplorerTree()
  521. {
  522. Initialize();
  523. }
  524. public ExplorerTree(bool optimizeMemoryUsage)
  525. {
  526. Initialize();
  527. this.optimizeMemoryUsage = optimizeMemoryUsage;
  528. }
  529. void Initialize()
  530. {
  531. // Obtain IShellFolder interface for shell root folder
  532. IShellFolder folder;
  533. WindowsAPI.SHGetDesktopFolder(out folder);
  534. Debug.Assert(folder != null);
  535. desktopFolder = new ShellIFolder(folder);
  536. // Get a pointer to the Shell Memory allocator
  537. WindowsAPI.SHGetMalloc(out iMalloc);
  538. Debug.Assert(iMalloc!=null);
  539. }
  540. void InitializeImageList()
  541. {
  542. // Get System Image List
  543. SHFILEINFO shfi = new SHFILEINFO();
  544. // Initialie idList by obtaining it from the shell 
  545.             IntPtr idHandle = IntPtr.Zero;
  546. WindowsAPI.SHGetSpecialFolderLocation(Handle, ShellSpecialFolder.CSIDL_DESKTOP, out idHandle);
  547. ShellIDList idList = new ShellIDList(idHandle, Handle);
  548. hSystemImageList = WindowsAPI.SHGetFileInfo(idList, 0, out shfi, (uint)Marshal.SizeOf(typeof(SHFILEINFO)), 
  549. ShellFileInfoFlags.SHGFI_SYSICONINDEX | ShellFileInfoFlags.SHGFI_SMALLICON );
  550. Debug.Assert(hSystemImageList != IntPtr.Zero);
  551. // Don't let the Garbage collector to dispose of these resource
  552. // when calling the Finalize methods. The garbage collector runs on a different
  553. // thread and calling the SHGetMalloc does seem to like this
  554. idList.Dispose();
  555. }
  556. ~ExplorerTree()
  557. {
  558. // Make sure we relese the shell memory allocator
  559. if ( iMalloc!=null )
  560. {
  561. IUnknown iUnknown = (IUnknown)iMalloc;
  562. iUnknown.Release();
  563. }
  564. }
  565. #endregion
  566. #region Overrides
  567. protected override void OnHandleCreated(EventArgs e)
  568. {
  569. base.OnHandleCreated(e);
  570. // Tree control has been created
  571. InitializeImageList();
  572. // Associate the System image list with the tree control
  573. WindowsAPI.SendMessage(Handle, (int)TreeViewMessages.TVM_SETIMAGELIST, 
  574. (int)TreeViewImageListFlags.TVSIL_NORMAL, (int)hSystemImageList);
  575. }
  576. protected override void OnMouseDown(MouseEventArgs e)
  577. {
  578. base.OnMouseDown(e);
  579. // Check for right click
  580. if (e.Button == MouseButtons.Right)
  581. {
  582. OnContextMenu(e);
  583. }
  584. }
  585. protected override void OnTreeViewItemExpanding(ref Message m)
  586. {
  587. Cursor = Cursors.WaitCursor;
  588. NM_TREEVIEW nmtv = (NM_TREEVIEW) m.GetLParam(typeof(NM_TREEVIEW));
  589. IntPtr currentHandle = nmtv.itemNew.hItem;
  590. // Get corresponding TreeNode object
  591. TreeNode tn = (TreeNode)hashTable[currentHandle];
  592. // Process any valid node except the root node
  593. if ( tn != null && tn.Parent != null )
  594. {
  595. TreeNodeData tnd = (TreeNodeData)tn.Tag;
  596. if ( optimizeMemoryUsage || tnd.Expanded == false || IsRemovableMedia(tnd.IDList) )
  597. {
  598. Debug.Assert(tnd!=null);
  599. if ( nmtv.action == (int)TreeViewItemExpansion.TVE_EXPAND )
  600. {
  601. EnumerateChildrenNodes(tn, tnd.IDList, tnd.Flags);
  602. tnd.Expanded = true;
  603. }
  604. }
  605. }
  606. // So that expansion takes place
  607. m.Result = IntPtr.Zero;
  608. Cursor = Cursors.Default;
  609. }
  610. protected override void OnTreeViewItemExpanded(ref Message m)
  611. {
  612. Cursor = Cursors.WaitCursor;
  613. NM_TREEVIEW nmtv = (NM_TREEVIEW) m.GetLParam(typeof(NM_TREEVIEW));
  614. IntPtr currentHandle = nmtv.itemNew.hItem;
  615. // Get corresponding TreeNode object
  616. TreeNode tn = (TreeNode)hashTable[currentHandle];
  617. TreeNodeData tnd = null;
  618. if ( tn != null )
  619. {
  620. tnd = (TreeNodeData)tn.Tag;
  621. Debug.Assert(tnd!=null);
  622. }
  623. // Process any valid node except the root node
  624. if ( (tn != null && tn.Parent != null && optimizeMemoryUsage) || IsRemovableMedia(tnd.IDList) )
  625. {
  626. if ( nmtv.action == (int)TreeViewItemExpansion.TVE_COLLAPSE )
  627. {
  628. DeleteChildren(tn);
  629. }
  630. }
  631. // So that expansion takes place
  632. m.Result = IntPtr.Zero;
  633. Cursor = Cursors.Default;
  634.             
  635. }
  636. protected override void OnTreeViewSelectionChanging(ref Message m)
  637. {
  638. // Fire event that contains the TreeNodeData class
  639. NM_TREEVIEW nmtv = (NM_TREEVIEW) m.GetLParam(typeof(NM_TREEVIEW));
  640. IntPtr currentHandle = nmtv.itemNew.hItem;
  641. // Get associated TreeNodeData object
  642. TreeNode treeNode = (TreeNode)hashTable[currentHandle];
  643. // We should always get a valid object
  644. TreeNodeData tnd = (TreeNodeData)treeNode.Tag;
  645. Debug.Assert(tnd!=null);
  646. // Fire event containing a copy of the object
  647. FireExplorerNodeChanged(tnd);
  648.       
  649. }
  650. protected override  void WndProc(ref Message message)
  651. {
  652. switch (message.Msg)
  653. {
  654. case (int)Msg.WM_COMMAND:
  655. InvokeCommand(message.WParam.ToInt32());
  656. break;
  657. case (int)Msg.WM_DESTROY:
  658. OnDestroyTree();
  659. break;
  660. default:
  661. break;
  662. }
  663. base.WndProc(ref message);
  664. }
  665. #endregion
  666. #region Properties
  667. public bool OptimizeMemoryUsage
  668. {
  669. get { return optimizeMemoryUsage; }
  670. }
  671. public IntPtr SystemImageList
  672. {
  673. get { return hSystemImageList; }
  674. }
  675. public TreeNodeFlags TreeNodeFlags
  676. {
  677. set { treeNodeFlags = value; }
  678. get { return treeNodeFlags; }
  679. }
  680. #endregion
  681. #region Methods
  682. public void InsertRootFolder(bool expand)
  683. {
  684. // If there is already a tree node delete it
  685. RemoveRootFolder();
  686. // Get Desktop IDList
  687. IntPtr idHandle = IntPtr.Zero;
  688. WindowsAPI.SHGetSpecialFolderLocation(Handle, ShellSpecialFolder.CSIDL_DESKTOP, out idHandle);
  689. ShellIDList idList = new ShellIDList(idHandle, Handle);
  690. // Insert Root Node
  691. TreeNode tn = InsertNode(null, desktopFolder, null, idList, treeNodeFlags);
  692.             
  693. // Enumerate Root Node children
  694. if ( expand )
  695. {
  696. // Insert children
  697. EnumerateChildrenNodes(tn, idList, treeNodeFlags);
  698. // Expand the tree
  699. tn.Expand();
  700. }
  701. // Release in this thread not the Garbage collector thread
  702. idList.Dispose();
  703. // Save the root node for later use
  704. rootTreeNode = tn;
  705. }
  706. public void RemoveRootFolder()
  707. {
  708. // Remove the root
  709. if ( rootTreeNode != null )
  710. {
  711. DeleteNode(rootTreeNode);
  712. rootTreeNode = null;
  713. }
  714. }
  715. #endregion
  716. #region Events
  717. public event ExplorerNodeChangedHandler ExplorerNodeChanged;
  718. #endregion
  719. #region Implementation
  720. TreeNode InsertNode( TreeNode parentNode, ShellIFolder parentFolder, 
  721. ShellIDList parentList, ShellIDList idList, TreeNodeFlags flags)
  722. {
  723. // Tree Node item to insert
  724. TVITEM item = new TVITEM();
  725. // Prepare helper object for this node
  726. TreeNodeData tnd = new TreeNodeData();
  727. tnd.Folder = parentFolder;
  728. tnd.Flags = flags;
  729. Debug.Assert(iMalloc!=null);
  730. tnd.IDList = ShellIDList.Combine(iMalloc, parentList, idList);
  731. Debug.Assert(tnd.IsValid());
  732. // Setup which fields we are going to use
  733. TreeViewItemFlags itemFlags = TreeViewItemFlags.TVIF_TEXT | TreeViewItemFlags.TVIF_IMAGE
  734. | TreeViewItemFlags.TVIF_SELECTEDIMAGE | TreeViewItemFlags.TVIF_CHILDREN | TreeViewItemFlags.TVIF_PARAM;
  735. item.mask = (uint)(itemFlags);
  736. string nodeText = string.Empty;
  737. SetupTreeViewItem(ref item, tnd, out nodeText);
  738.             // Insert item using the Nodes collection
  739. TreeNode treeNode = null;
  740. if ( parentNode == null )
  741. {
  742. // ROOT node
  743. treeNode = Nodes.Add(nodeText);
  744. }
  745. else
  746. {
  747. // This is a child node, add it to the parent
  748.                treeNode = parentNode.Nodes.Add(nodeText);
  749.   }
  750. IntPtr handle = treeNode.Handle;
  751. tnd.Handle = handle;
  752. // Save item data in the Tag bucket
  753. treeNode.Tag = tnd;
  754.                         
  755. // Set some flags for the node we are inserting
  756. item.hItem = handle;
  757. // We are going to use the lParam when sorting
  758. item.lParam = handle;
  759. SetItem(ref item);
  760. // Release previously allocated memory if necessary
  761. if ( item.pszText != IntPtr.Zero )
  762. Marshal.FreeHGlobal(item.pszText);
  763. // Add it to the hash table for easy mapping and retrieval
  764. hashTable.Add(handle, treeNode);
  765. return treeNode;
  766. }
  767. void SetupTreeViewItem(ref TVITEM item, TreeNodeData tnd, out string path)
  768. {
  769. // Initialize out parameter
  770. path = string.Empty;
  771. // Get TreeNodeData
  772. Debug.Assert(tnd!=null && tnd.IsValid());
  773. // Relative pIDList
  774. IntPtr idRel = tnd.IDList.GetLastChild();
  775. if ( idRel == IntPtr.Zero )
  776. {
  777. // If the parent ID List has not children
  778. // set the relative ID List to the parent list
  779. idRel = tnd.IDList.Handle;
  780. }
  781. // If we need to show the node name
  782. if ( (item.mask & (uint)TreeViewItemFlags.TVIF_TEXT) != 0)
  783. {
  784. if ( (tnd.Flags & TreeNodeFlags.ShowPath) != 0 )
  785. {
  786. // Get full path
  787. path = tnd.IDList.GetPath();
  788. if ( path != string.Empty && (tnd.Flags & TreeNodeFlags.ShowName) != 0 )
  789. {
  790. // Get just the name of the node
  791. int slashIndex = path.LastIndexOf('\');
  792. path = path.Substring(slashIndex+1);
  793. }
  794. }
  795. // If we could not get a path, try getting a global name
  796. if ( path == string.Empty )
  797. {
  798. ShellGetDisplayNameOfFlags flags = ShellGetDisplayNameOfFlags.SHGDN_INFOLDER;
  799. if ( (tnd.Flags & TreeNodeFlags.ShowName) != 0 )
  800. flags = ShellGetDisplayNameOfFlags.SHGDN_NORMAL;
  801. flags |= ShellGetDisplayNameOfFlags.SHGDN_INCLUDE_NONFILESYS;
  802. path = tnd.Folder.GetDisplayNameOf(idRel, flags);
  803. }
  804. // Set the Node text
  805. if ( path != string.Empty )
  806. item.pszText = Marshal.StringToHGlobalAuto(path);
  807. }
  808. // If we need to show node image
  809. if ( (item.mask & (uint)(TreeViewItemFlags.TVIF_IMAGE |
  810. TreeViewItemFlags.TVIF_SELECTEDIMAGE)) !=  0)
  811. {
  812. GetAttributeOfFlags attributes = GetAttributeOfFlags.SFGAO_FOLDER | GetAttributeOfFlags.SFGAO_LINK 
  813. | GetAttributeOfFlags.SFGAO_SHARE | GetAttributeOfFlags.SFGAO_GHOSTED;
  814. tnd.Folder.GetAttributesOf(1, ref idRel, out attributes);
  815. // set correct icon
  816. if ( (attributes & GetAttributeOfFlags.SFGAO_GHOSTED) != 0)
  817. {
  818. item.mask  |= (uint)ListViewItemFlags.LVIF_STATE;
  819. item.stateMask |= (uint)ListViewItemState.LVIS_CUT;
  820. item.state |= (uint)ListViewItemState.LVIS_CUT;
  821. }
  822. if ( (attributes & GetAttributeOfFlags.SFGAO_SHARE) != 0 )
  823. {
  824. item.mask |= (uint)ListViewItemFlags.LVIF_STATE;
  825. item.state &= ~(uint)ListViewItemState.LVIS_OVERLAYMASK;
  826. item.state |= WindowsAPI.INDEXTOOVERLAYMASK(1);
  827. item.stateMask |= (uint)ListViewItemState.LVIS_OVERLAYMASK;
  828. }
  829. else if ( (attributes & GetAttributeOfFlags.SFGAO_LINK) != 0)
  830. {
  831. item.mask |= (uint)ListViewItemFlags.LVIF_STATE;
  832. item.state &= ~(uint)ListViewItemState.LVIS_OVERLAYMASK;
  833. item.state |= WindowsAPI.INDEXTOOVERLAYMASK(2);
  834. item.stateMask |= (uint)ListViewItemState.LVIS_OVERLAYMASK;
  835. }
  836. if ( (item.mask & (uint)(TreeViewItemFlags.TVIF_IMAGE)) != 0 )
  837. {
  838. item.iImage = tnd.IDList.GetIconIndex(ShellFileInfoFlags.SHGFI_SMALLICON);
  839. item.iSelectedImage = item.iImage;
  840. }
  841. if ( ((item.mask & (uint)TreeViewItemFlags.TVIF_SELECTEDIMAGE) != 0)
  842. && ((attributes & GetAttributeOfFlags.SFGAO_FOLDER) != 0) )
  843. {
  844. item.iSelectedImage = tnd.IDList.GetIconIndex(ShellFileInfoFlags.SHGFI_SMALLICON
  845. | ShellFileInfoFlags.SHGFI_OPENICON);
  846. }
  847. }
  848. // If the node has children and is a folder
  849. if ( (item.mask & (uint)TreeViewItemFlags.TVIF_CHILDREN) != 0 )
  850. {
  851. GetAttributeOfFlags attributes = GetAttributeOfFlags.SFGAO_FOLDER;
  852. tnd.Folder.GetAttributesOf(1, ref idRel, out attributes);
  853. // Get children
  854. item.cChildren = 0;
  855. if ( (attributes & GetAttributeOfFlags.SFGAO_FOLDER) != 0 )
  856. {
  857. if ( (tnd.Flags & TreeNodeFlags.ShowFiles) != 0 )
  858. item.cChildren = 1;
  859. else if ( (attributes & GetAttributeOfFlags.SFGAO_REMOVABLE) != 0 )
  860. item.cChildren = 1;
  861. else
  862. {
  863. attributes = GetAttributeOfFlags.SFGAO_HASSUBFOLDER;
  864. tnd.Folder.GetAttributesOf(1,  ref idRel, out attributes);
  865. item.cChildren = ((attributes & GetAttributeOfFlags.SFGAO_HASSUBFOLDER) != 0) ? 1 : 0;
  866. }
  867. }
  868. }
  869. }
  870. void SetItem(ref TVITEM item)
  871. {
  872. // Only Windows NT based systems support for the moment
  873. WindowsAPI.SendMessage(Handle, TreeViewMessages.TVM_SETITEMW, 0, ref item);
  874. }
  875. void GetItem(ref TVITEM item)
  876. {
  877. // Only Windows NT based systems support for the moment
  878. WindowsAPI.SendMessage(Handle, TreeViewMessages.TVM_GETITEMW, 0, ref item);
  879. }
  880. void EnumerateChildrenNodes(TreeNode parentNode, ShellIDList idListParent, TreeNodeFlags flags)
  881. {
  882. ShellIFolder shellFolder = null;
  883. if ( idListParent.IsRootList() )
  884. {
  885. // Root object, just increase the reference count for the interface
  886. shellFolder = new ShellIFolder(desktopFolder);
  887. }
  888. else
  889. {
  890. // Not a root item, get a new IShellFolder interface bound to the idList
  891. shellFolder = ShellIFolder.CreateShellIFolder(desktopFolder, idListParent);
  892. }
  893. Debug.Assert(shellFolder!=null);
  894. // Get enumeration interface
  895. ShellEnumFlags enumFlags = ShellEnumFlags.SHCONTF_FOLDERS |
  896. ( ((flags & TreeNodeFlags.ShowFiles) != 0) ? ShellEnumFlags.SHCONTF_NONFOLDERS : 0) |
  897. ( ((flags & TreeNodeFlags.ShowHiddenFiles) != 0) ? ShellEnumFlags.SHCONTF_INCLUDEHIDDEN : 0);
  898. ShellIEnumIDList shellEnumIDList = null;
  899. try 
  900. {
  901. shellEnumIDList = ShellIEnumIDList.CreateShellIEnumIDList(Handle, shellFolder, enumFlags);
  902. }
  903. catch(Exception e)
  904. {
  905. // We are going to get an exception if we tried to enumerate a removable
  906. // media and the user did not insert a CD or other valid media
  907. Debug.WriteLine(e.Message);
  908. return;
  909. }
  910. // Do the insertion
  911. IntPtr idListPtr = IntPtr.Zero;
  912. uint fetched = 0;
  913. while ( (int)WinErrors.NOERROR == shellEnumIDList.Next(1, ref idListPtr, out fetched) )
  914. {
  915. ShellIDList idList = new ShellIDList(idListPtr);
  916. InsertNode(parentNode, shellFolder, idListParent, idList, flags);
  917. idList.Dispose();
  918. }
  919. // Sort the recently inserted children
  920. SortChildren(parentNode);
  921.                        
  922. }
  923. void DeleteChildren(TreeNode parentNode)
  924. {
  925. int count = parentNode.Nodes.Count;
  926. while ( count != 0 )
  927. {
  928. // Remove children nodes
  929. // Remove from hash table first
  930. TreeNode childNode = (TreeNode)parentNode.Nodes[0];
  931. // Actively release the shell Memory allocated here
  932. // -- Letting the garbage collector do it from its thread 
  933. // causes an exception --
  934. TreeNodeData tnd = (TreeNodeData)childNode.Tag;
  935. Debug.Assert(tnd!=null);
  936. tnd.IDList.Dispose();
  937. // Recursively delete the children of
  938. // the child node
  939. DeleteChildren(childNode);
  940. hashTable.Remove(childNode.Handle);
  941. // Remove from the collection
  942. parentNode.Nodes.RemoveAt(0);
  943. count = parentNode.Nodes.Count;
  944. }
  945. ResetChildrenCountFlag(parentNode.Handle);
  946. }
  947. void DeleteNode(TreeNode treeNode)
  948. {
  949. bool hasChildren = false;
  950. IntPtr handle = treeNode.Handle;
  951. if ( treeNode.Nodes.Count != 0 )
  952. {
  953. hasChildren = true;
  954. DeleteChildren(treeNode);
  955. }
  956. // Now delete this node from the collection
  957. Nodes.Remove(treeNode);
  958. // Actively release the shell Memory allocated here
  959. // -- Letting the garbage collector do it from its thread 
  960. // causes an exception --
  961. TreeNodeData tnd = (TreeNodeData)treeNode.Tag;
  962. Debug.Assert(tnd!=null);
  963. tnd.IDList.Dispose();
  964. hashTable.Remove(handle);
  965. if ( hasChildren )
  966. ResetChildrenCountFlag(handle);
  967.             
  968. }
  969. void ResetChildrenCountFlag(IntPtr hItem)
  970. {
  971. // Since we are wiping out all children
  972. // we need to reset the style of the node
  973. // so that it shows the plus sign 
  974. TVITEM item = new TVITEM();
  975. item.hItem = hItem;
  976. item.mask |= (uint)(TreeViewItemFlags.TVIF_CHILDREN | TreeViewItemFlags.TVIF_HANDLE);
  977. item.cChildren = 1;
  978. SetItem(ref item);
  979. }
  980. void SortChildren(TreeNode parentNode)
  981. {
  982. TVSORTCB tvscb = new TVSORTCB();
  983.             tvscb.hParent = parentNode.Handle;
  984. tvscb.lpfnCompare = new WindowsAPI.CompareFunc(OnCompare);
  985. // Pin the delegate object so that it can be passed
  986. // to unmanaged code without risk of the garbage collector free its memory
  987. GCHandle compareHandle = GCHandle.Alloc(tvscb.lpfnCompare);
  988. // Now do the sorting
  989. WindowsAPI.SendMessage(Handle, TreeViewMessages.TVM_SORTCHILDRENCB, 0, ref tvscb);
  990. // Free our handle
  991. compareHandle.Free();
  992.             
  993. }
  994. int OnCompare(IntPtr param1, IntPtr param2, IntPtr sortParam)
  995. {
  996. // Do the comparison
  997.             TreeNode tn1 = (TreeNode)hashTable[param1];
  998.             TreeNode tn2 = (TreeNode)hashTable[param2];
  999. Debug.Assert(tn1!=null && tn2!=null);
  1000. TreeNodeData tnd1 = (TreeNodeData)tn1.Tag;
  1001. TreeNodeData tnd2 = (TreeNodeData)tn2.Tag;
  1002. Debug.Assert(tnd1!=null && tnd2!=null);
  1003. // Sort items by comparing using the parent
  1004. // folder to compare their IDs
  1005. ShellIFolder folder = tnd1.Folder;
  1006. Debug.Assert(folder!=null);
  1007.             return folder.CompareIDs(0, tnd1.IDList, tnd2.IDList);            
  1008. }
  1009. bool IsRemovableMedia(ShellIDList idList)
  1010. {
  1011. StringBuilder path = new StringBuilder(1024);
  1012.             int result = WindowsAPI.SHGetPathFromIDList(idList, path);
  1013. if ( result != 0 )
  1014. {
  1015. string drivePath = path.ToString();
  1016. drivePath = drivePath.Substring(0,2);
  1017. DriveType driveType = (DriveType)WindowsAPI.GetDriveType(drivePath);
  1018. if ( driveType == DriveType.DRIVE_CDROM || driveType == DriveType.DRIVE_REMOVABLE )
  1019. return true;
  1020. }
  1021. return false;
  1022.             
  1023. }
  1024. void OnContextMenu(MouseEventArgs e)
  1025. {
  1026. // Display shell context menu for the item that was clicked
  1027. TreeViewHitTestFlags htFlags;
  1028. IntPtr hItem = HitTest(new Point(e.X, e.Y), out htFlags);
  1029. // If we did not click on an item, just return
  1030. if ( (htFlags & TreeViewHitTestFlags.TVHT_ONITEM) == 0 )
  1031. return;
  1032.             
  1033. // Should have a valid item
  1034. Debug.Assert(hItem!=IntPtr.Zero);
  1035. TreeNode tn = (TreeNode)hashTable[hItem];
  1036. Debug.Assert(tn!=null);
  1037. // Select this node
  1038. SelectedNode = tn;
  1039. // Get item shellInformation
  1040. TreeNodeData tnd = (TreeNodeData)tn.Tag;
  1041. Debug.Assert(tnd!=null);
  1042.             
  1043.             // Get clicked item context menu
  1044. contextMenu.Initialize(tnd.Folder, tnd.IDList);
  1045. // Now display the menu
  1046. contextMenu.Show(this, new Point(e.X, e.Y));
  1047. }
  1048. void InvokeCommand(int commandID)
  1049. {
  1050. contextMenu.InvokeCommand(commandID);
  1051. }
  1052. void FireExplorerNodeChanged(TreeNodeData tnd)
  1053. {
  1054. if ( ExplorerNodeChanged != null )
  1055. {
  1056. ExplorerNodeChanged(this, tnd);
  1057. }
  1058. }
  1059. void OnDestroyTree()
  1060. {
  1061. // Delete all the nodes here so that
  1062. // we dispose of the memory allocated by IMalloc here
  1063. // in the main UI thread instead of letting the Garbage collector
  1064. // to call the dispose method itself since it seems to be 
  1065. // a problem with COM thread initialization
  1066. if ( rootTreeNode != null )
  1067. {
  1068. DeleteChildren(rootTreeNode);
  1069. }
  1070. }
  1071. #endregion
  1072. }
  1073. }