/lib/CTreeViewControl.ahk

http://github.com/Skiouros/Macro · AutoHotKey · 590 lines · 375 code · 17 blank · 198 comment · 104 complexity · 139c9f701bff50daf8b5f95dc0b1833f MD5 · raw file

  1. /*
  2. Class: CTreeViewControl
  3. A TreeView control.
  4. This control extends <CControl>. All basic properties and functions are implemented and documented in this class.
  5. */
  6. Class CTreeViewControl Extends CControl
  7. {
  8. __New(Name, ByRef Options, Text, GUINum)
  9. {
  10. ;~ global CGUI
  11. Events := ["_Click", "_RightClick", "_EditingStart", "_FocusReceived", "_FocusLost", "_KeyPress", "_ItemExpanded", "_ItemCollapsed"]
  12. if(!InStr(Options, "AltSubmit")) ;Automagically add AltSubmit when necessary
  13. {
  14. for index, function in Events
  15. {
  16. if(IsFunc(CGUI.GUIList[GUINum][Name Function]))
  17. {
  18. Options .= " AltSubmit"
  19. break
  20. }
  21. }
  22. }
  23. base.__New(Name, Options, Text, GUINum)
  24. this._.Insert("ControlStyles", {Checked : 0x100, ReadOnly : -0x8, FullRowSelect : 0x1000, Buttons : 0x1, Lines : 0x2, HScroll : -0x8000, AlwaysShowSelection : 0x20, SingleExpand : 0x400, HotTrack : 0x200})
  25. this._.Insert("Events", ["DoubleClick", "EditingEnd", "ItemSelected", "Click", "RightClick", "EditingStart", "KeyPress", "ItemExpanded", "ItemCollapsed", "FocusReceived", "FocusLost"])
  26. this._.Insert("Messages", {0x004E : "Notify"}) ;This control uses WM_NOTIFY with NM_SETFOCUS and NM_KILLFOCUS
  27. this.Type := "TreeView"
  28. }
  29. PostCreate()
  30. {
  31. Base.PostCreate()
  32. this._.ImageListManager := new this.CImageListManager(this.GUINum, this.hwnd)
  33. this._.Items := new this.CItem(0, this.GUINum, this.hwnd)
  34. this._.PreviouslySelectedItem := this._.Items
  35. }
  36. /*
  37. Function: FindItem
  38. Finds an item by its ID.
  39. Parameters:
  40. ID - The ID of the item.
  41. */
  42. FindItem(ID, Root = "")
  43. {
  44. if(!ID) ;Root node
  45. return this.Items
  46. if(!IsObject(Root))
  47. Root := this.Items
  48. if(ID = Root.ID)
  49. return Root
  50. Loop % Root.MaxIndex()
  51. if(result := this.FindItem(ID, Root[A_Index]))
  52. return result
  53. return 0
  54. }
  55. /*
  56. Variable: Items
  57. Contains the nodes of the tree. Each level can be iterated and indexed. A node is of type <CTreeViewControl.CItem>
  58. Variable: SelectedItem
  59. Contains the node of type <CItem> that is currently selected.
  60. Variable: PreviouslySelectedItem
  61. Contains the node of type <CItem> that was previously selected.
  62. */
  63. __Get(Name, Params*)
  64. {
  65. ;~ global CGUI
  66. if(Name = "Items")
  67. Value := this._.Items
  68. else if(Name = "SelectedItem")
  69. {
  70. GUI := CGUI.GUIList[this.GUINum]
  71. if(GUI.IsDestroyed)
  72. return
  73. Gui, % this.GUINum ":Default"
  74. Gui, TreeView, % this.ClassNN
  75. Value := this.FindItem(TV_GetSelection())
  76. }
  77. Loop % Params.MaxIndex()
  78. if(IsObject(Value)) ;Fix unlucky multi parameter __GET
  79. Value := Value[Params[A_Index]]
  80. if(Value)
  81. return Value
  82. }
  83. __Set(Name, Value)
  84. {
  85. ;~ global CGUI
  86. if(!CGUI.GUIList[this.GUINum].IsDestroyed)
  87. {
  88. DetectHidden := A_DetectHiddenWindows
  89. DetectHiddenWindows, On
  90. Handled := true
  91. if(Name = "SelectedItem")
  92. {
  93. GUI := CGUI.GUIList[this.GUINum]
  94. Gui, % this.GUINum ":Default"
  95. Gui, TreeView, % this.ClassNN
  96. TV_Modify(Value._.ID)
  97. this.ProcessSubControlState(this._.PreviouslySelectedItem, this.SelectedItem)
  98. this._.PreviouslySelectedItem := this.SelectedItem
  99. }
  100. else
  101. Handled := false
  102. if(!DetectHidden)
  103. DetectHiddenWindows, Off
  104. if(Handled)
  105. return Value
  106. }
  107. }
  108. /*
  109. Event: Introduction
  110. To handle control events you need to create a function with this naming scheme in your window class: ControlName_EventName(params)
  111. The parameters depend on the event and there may not be params at all in some cases.
  112. Additionally it is required to create a label with this naming scheme: GUIName_ControlName
  113. GUIName is the name of the window class that extends CGUI. The label simply needs to call CGUI.HandleEvent().
  114. For better readability labels may be chained since they all execute the same code.
  115. Instead of using ControlName_EventName() you may also call <CControl.RegisterEvent> on a control instance to register a different event function name.
  116. Event: Click(Item)
  117. Invoked when the user clicked on the control.
  118. Event: DoubleClick(Item)
  119. Invoked when the user double-clicked on the control.
  120. Event: RightClick(Item)
  121. Invoked when the user right-clicked on the control.
  122. Event: EditingStart(Item)
  123. Invoked when the user started editing a node.
  124. Event: EditingEnd(Item)
  125. Invoked when the user finished editing a node.
  126. Event: ItemSelected(Item)
  127. Invoked when the user selected a node.
  128. Event: ItemExpanded(Item)
  129. Invoked when the user expanded a node.
  130. Event: ItemCollapsed(Item)
  131. Invoked when the user collapsed a node.
  132. Event: KeyPress(KeyCode)
  133. Invoked when the user pressed a key while the control was focused.
  134. */
  135. HandleEvent(Event)
  136. {
  137. ;~ global CGUI
  138. if(CGUI.GUIList[this.GUINum].IsDestroyed)
  139. return
  140. ;Handle visibility of controls associated with tree nodees
  141. if(Event.GUIEvent = "S")
  142. {
  143. this.ProcessSubControlState(this.PreviouslySelectedItem, this.SelectedItem)
  144. }
  145. if(Event.GUIEvent == "E")
  146. this.CallEvent("EditingStart", this.Items.ItemByID(Event.EventInfo))
  147. else if(EventName := {DoubleClick : "DoubleClick", e : "EditingEnd", S : "ItemSelected", Normal : "Click", RightClick : "RightClick", "+" : "ItemExpanded", "-" : "ItemCollapsed"}[Event.GUIEvent])
  148. this.CallEvent(EventName, this.Items.ItemByID(Event.EventInfo))
  149. else if(EventName = "K")
  150. this.CallEvent(EventName, Event.EventInfo)
  151. else if(Event.GUIEvent == "F")
  152. this.CallEvent("FocusReceived")
  153. else if(Event.GUIEvent == "f")
  154. this.CallEvent("FocusLost")
  155. if(Event.GUIEvent = "S")
  156. this.PreviouslySelectedItem := this.SelectedItem
  157. }
  158. /*
  159. Class: CTreeViewControl.CItem
  160. A tree node.
  161. */
  162. Class CItem
  163. {
  164. __New(ID, GUINum, hwnd)
  165. {
  166. this.Insert("_", {})
  167. this._.Insert("GUINum", GUINum)
  168. this._.Insert("hwnd", hwnd)
  169. this._.Insert("ID", ID)
  170. this._.Insert("Controls", {})
  171. }
  172. /*
  173. Function: Add
  174. Adds a new item to the TreeView.
  175. Parameters:
  176. Text - The text of the item.
  177. Options - Various options, see Autohotkey TreeView documentation
  178. Returns:
  179. An object of type CItem representing the newly added item.
  180. */
  181. Add(Text, Options = "")
  182. {
  183. ;~ global CGUI, CTreeViewControl
  184. GUI := CGUI.GUIList[this._.GUINum]
  185. if(GUI.IsDestroyed)
  186. return
  187. Control := GUI.Controls[this._.hwnd]
  188. Gui, % this._.GUINum ":Default"
  189. Gui, TreeView, % Control.ClassNN
  190. ID := TV_Add(Text, this.ID, Options)
  191. Item := new CTreeViewControl.CItem(ID, this._.GUINum, this._.hwnd)
  192. ;~ Item.Icon := ""
  193. this.Insert(Item)
  194. return Item
  195. }
  196. /*
  197. Function: AddControl
  198. Adds a control to this tree node that will be visible only when this node is selected. The parameters correspond to the Add() function of CGUI.
  199. Parameters:
  200. Type - The type of the control.
  201. Name - The name of the control.
  202. Options - Options used for creating the control.
  203. Text - The text of the control.
  204. UseEnabledState - If true, the control will be enabled/disabled instead of visible/hidden.
  205. */
  206. AddControl(type, Name, Options, Text, UseEnabledState = 0)
  207. {
  208. ;~ global CGUI
  209. GUI := CGUI.GUIList[this._.GUINum]
  210. if(!this.Selected)
  211. Options .= UseEnabledState ? " Disabled" : " Hidden"
  212. Control := GUI.AddControl(type, Name, Options, Text, this._.Controls)
  213. Control._.UseEnabledState := UseEnabledState
  214. Control.hParentControl := this._.hwnd
  215. return Control
  216. }
  217. /*
  218. Function: Remove
  219. Removes an item.
  220. Parameters:
  221. ObjectOrIndex - The item object or the index of the child item of this.
  222. */
  223. Remove(ObjectOrIndex)
  224. {
  225. ;~ global CGUI
  226. GUI := CGUI.GUIList[this._.GUINum]
  227. if(GUI.IsDestroyed)
  228. return
  229. Control := GUI.Controls[this._.hwnd]
  230. Gui, % this._.GUINum ":Default"
  231. Gui, TreeView, % Control.ClassNN
  232. if(!IsObject(ObjectOrIndex)) ;If index, get object and then handle
  233. ObjectOrIndex := this[ObjectOrIndex]
  234. if(ObjectOrIndex.ID = 0) ;Don't delete root node
  235. return
  236. if(ObjectOrIndex.Selected)
  237. WasSelected := true
  238. p := ObjectOrIndex.Parent
  239. for Index, Item in ObjectOrIndex.Parent
  240. if(Item = ObjectOrIndex)
  241. {
  242. ObjectOrIndex.Parent._Remove(A_Index)
  243. break
  244. }
  245. TV_Delete(ObjectOrIndex.ID)
  246. if(WasSelected)
  247. {
  248. Control.ProcessSubControlState(ObjectOrIndex, this.SelectedItem)
  249. Control.PreviouslySelectedItem := ObjectOrIndex ;The node is accessible here even though it does not exist anymore because the user might have stored data in it that might need to be used in _ItemSelected handler.
  250. }
  251. if(TV_GetCount() = 0) ;If all TreeView items are deleted, fire a selection changed event
  252. if(IsFunc(GUI[Control.Name "_ItemSelected"]))
  253. {
  254. ErrorLevel := ErrLevel
  255. GUI[Control.Name "_ItemSelected"](Control.Items)
  256. if(!Critical)
  257. Critical, Off
  258. return
  259. }
  260. }
  261. /*
  262. Function: Move
  263. Moves an Item to another position.
  264. Parameters:
  265. Position - The new (one-based) - position in the child items of Parent.
  266. Parent - The item will be inserted as child of the Parent item. Leave empty to use its current parent.
  267. */
  268. Move(Position=1, Parent = "")
  269. {
  270. ;~ global CGUI
  271. GUI := CGUI.GUIList[this._.GUINum]
  272. if(GUI.IsDestroyed)
  273. return
  274. Control := GUI.Controls[this._.hwnd]
  275. Gui, % this._.GUINum ":Default"
  276. Gui, TreeView, % Control.ClassNN
  277. ;Backup properties which are stored in the TreeList itself
  278. Text := this.Text
  279. Bold := this.bold
  280. Expanded := this.Expanded
  281. Checked := this.Checked
  282. Selected := this.Selected
  283. OldID := this.ID
  284. ;If no parent is specified, the item will be moved on the current level
  285. if(!Parent)
  286. Parent := this.Parent
  287. OldParent := this.Parent
  288. ;Add new node. At this point there are two nodes.
  289. NewID := TV_Add(Text, Parent.ID, (Position = 1 ? "First" : Parent[Position-1].ID) " " (Bold ? "+Bold" : "") (Expanded ? "Expand" : "") (Checked ? "Check" : "") (Selected ? "Select" : ""))
  290. ;Collect all child items
  291. Childs := []
  292. for index, Item in this
  293. Childs.Insert(Item)
  294. this._.ID := NewID
  295. ;Remove old parent node link and set the new one
  296. if(OldParent != Parent)
  297. {
  298. for Index, Item in OldParent
  299. if(Item = this)
  300. {
  301. OldParent.Remove(A_Index)
  302. break
  303. }
  304. Parent.Insert(Position, this)
  305. }
  306. if(this.Icon)
  307. Control._.ImageListManager.SetIcon(this._.ID, this.Icon, this.IconNumber)
  308. ;Move child items
  309. for index, Item in Childs
  310. Item.Move(index, this)
  311. ;Delete old tree node
  312. TV_Delete(OldID)
  313. }
  314. /*
  315. Function: SetIcon
  316. Sets the icon of a tree node
  317. Parameters:
  318. Filename - The filename of the file containing the icon.
  319. IconNumberOrTransparencyColor - The icon number or the transparency color if the used file has no transparency support.
  320. */
  321. SetIcon(Filename, IconNumberOrTransparencyColor = 1)
  322. {
  323. ;~ global CGUI
  324. GUI := CGUI.GUIList[this._.GUINum]
  325. if(GUI.IsDestroyed)
  326. return
  327. Control := GUI.Controls[this._.hwnd]
  328. Control._.ImageListManager.SetIcon(this._.ID, Filename, IconNumberOrTransparencyColor)
  329. this._.Icon := Filename
  330. this._.IconNumber := IconNumberOrTransparencyColor
  331. }
  332. /*
  333. Function: MaxIndex
  334. Returns the number of child nodes.
  335. */
  336. MaxIndex()
  337. {
  338. ;~ global CGUI
  339. GUI := CGUI.GUIList[this._.GUINum]
  340. if(GUI.IsDestroyed)
  341. return
  342. Control := GUI.Controls[this._.hwnd]
  343. Gui, % this._.GUINum ":Default"
  344. Gui, TreeView, % Control.ClassNN
  345. current := this._.ID ? TV_GetChild(this._.ID) : TV_GetNext() ;Get first child or first top node
  346. if(!current)
  347. return 0 ;No children
  348. count := 0
  349. while(current && current := TV_GetNext(current))
  350. count++
  351. return count + 1
  352. }
  353. /*
  354. Function: ItemByID
  355. Access a child item by its ID.
  356. Parameters:
  357. ID - The ID of the child item
  358. */
  359. ;Access a child item by its ID
  360. ItemByID(ID)
  361. {
  362. Loop % this.MaxIndex()
  363. {
  364. if(this[A_Index]._.ID = ID)
  365. return this[A_Index]
  366. else if(Item := this[A_Index].ItemByID(ID))
  367. return Item
  368. }
  369. }
  370. _NewEnum()
  371. {
  372. ;~ global CEnumerator
  373. return new CEnumerator(this)
  374. }
  375. /*
  376. Variable: 1,2,3,4,...
  377. The child nodes of a tree node may be accessed by their index, e.g. this.TreeView1.Items[1][2][3].Text := "AHK"
  378. Variable: CheckedItems
  379. An array containing all checked child nodes of type <CTreeViewControl.CItem>.
  380. Variable: CheckedIndices
  381. An array containing all checked child indices.
  382. Variable: Parent
  383. The parent node of this node.
  384. Variable: ID
  385. The ID used internally in the TreeView control.
  386. Variable: Icon
  387. The path of an icon assigned to this node.
  388. Variable: IconNumber
  389. The icon number used when an icon file contains more than one icon.
  390. Variable: Count
  391. The number of child nodes.
  392. Variable: HasChildren
  393. True if there is at least one child node.
  394. Variable: Text
  395. The text of this tree node.
  396. Variable: Checked
  397. True if the tree node is checked.
  398. Variable: Selected
  399. True if the tree node is selected.
  400. Variable: Expanded
  401. True if the tree node is expanded.
  402. Variable: Bold
  403. If true, the text of this node is bold.
  404. */
  405. __Get(Name, Params*)
  406. {
  407. ;~ global CTreeViewControl, CGUI
  408. if(Name != "_")
  409. {
  410. GUI := CGUI.GUIList[this._.GUINum]
  411. if(!GUI.IsDestroyed)
  412. {
  413. ;~ if Name is Integer ;get a child node
  414. ;~ {
  415. ;~ if(Name <= this.MaxIndex())
  416. ;~ {
  417. ;~ Control := GUI.Controls[this._.hwnd]
  418. ;~ Gui, % this._.GUINum ":Default"
  419. ;~ Gui, TreeView, % Control.ClassNN
  420. ;~ child := TV_GetChild(this._.ID) ;Find child node id
  421. ;~ Loop % Name - 1
  422. ;~ child := TV_GetNext(child)
  423. ;~ Value := new CTreeViewControl.CItem(child, this._.GUINum, this._.hwnd)
  424. ;~ }
  425. ;~ }
  426. if(Name = "CheckedItems")
  427. {
  428. Value := []
  429. for index, Item in this
  430. if(Item.Checked)
  431. Value.Insert(Item)
  432. }
  433. else if(Name = "CheckedIndices")
  434. {
  435. Value := []
  436. for index, Item in this
  437. if(Item.Checked)
  438. Value.Insert(index)
  439. }
  440. else if(Name = "Parent")
  441. {
  442. Control := GUI.Controls[this._.hwnd]
  443. Gui, % this._.GUINum ":Default"
  444. Gui, TreeView, % Control.ClassNN
  445. VaLue := Control.FindItem(TV_GetParent(this._.ID))
  446. }
  447. else if(Name = "ID" || Name = "Icon" || Name = "IconNumber")
  448. Value := this._[Name]
  449. else if(Name = "Count")
  450. Value := this.MaxIndex()
  451. else if(Name = "HasChildren")
  452. {
  453. Control := GUI.Controls[this._.hwnd]
  454. Gui, % this._.GUINum ":Default"
  455. Gui, TreeView, % Control.ClassNN
  456. Value := TV_GetChild(this._.ID) > 0
  457. }
  458. else if(Name = "Text")
  459. {
  460. Control := GUI.Controls[this._.hwnd]
  461. Gui, % this._.GUINum ":Default"
  462. Gui, TreeView, % Control.ClassNN
  463. TV_GetText(Value, this._.ID)
  464. }
  465. else if(Name = "Checked" || Name = "Expanded" || Name = "Bold")
  466. {
  467. Control := GUI.Controls[this._.hwnd]
  468. Gui, % this._.GUINum ":Default"
  469. Gui, TreeView, % Control.ClassNN
  470. Value := TV_Get(this._.ID, Name) > 0
  471. }
  472. else if(Name = "Selected")
  473. {
  474. Control := GUI.Controls[this._.hwnd]
  475. Gui, % this._.GUINum ":Default"
  476. Gui, TreeView, % Control.ClassNN
  477. Value := TV_GetSelection() = this._.ID
  478. }
  479. else if(Name = "Controls")
  480. Value := this._.Controls
  481. Loop % Params.MaxIndex()
  482. if(IsObject(Value)) ;Fix unlucky multi parameter __GET
  483. Value := Value[Params[A_Index]]
  484. if(Value)
  485. return Value
  486. }
  487. }
  488. }
  489. __Set(Name, Params*)
  490. {
  491. ;~ global CGUI
  492. Value := Params[Params.MaxIndex()]
  493. Params.Remove(Params.MaxIndex())
  494. GUI := CGUI.GUIList[this._.GUINum]
  495. if(!GUI.IsDestroyed)
  496. {
  497. if(Name = "Text")
  498. {
  499. Control := GUI.Controls[this._.hwnd]
  500. Gui, % this._.GUINum ":Default"
  501. Gui, TreeView, % Control.ClassNN
  502. TV_Modify(this._.ID, "", Value)
  503. return Value
  504. }
  505. else if(Name = "Selected") ;Deselecting is not possible it seems
  506. {
  507. if(Value = 1)
  508. {
  509. Control := GUI.Controls[this._.hwnd]
  510. Gui, % this._.GUINum ":Default"
  511. Gui, TreeView, % Control.ClassNN
  512. TV_Modify(this._.ID)
  513. Control.ProcessSubControlState(Control._.PreviouslySelectedItem, Control.SelectedItem)
  514. Control._.PreviouslySelectedItem := Control.SelectedItem
  515. }
  516. return Value
  517. }
  518. else if(Option := {Checked : "Check", Expanded : "Expand", Bold : "Bold"}[Name]) ;Wee, check and remapping in one step
  519. {
  520. Control := GUI.Controls[this._.hwnd]
  521. Gui, % this._.GUINum ":Default"
  522. Gui, TreeView, % Control.ClassNN
  523. TV_Modify(this._.ID, (Value = 1 ? "+" : "-") Option)
  524. }
  525. else if(Name = "Icon")
  526. {
  527. this.SetIcon(Value, this._.HasKey("IconNumber") ? this._.IconNumber : 1)
  528. return Value
  529. }
  530. else if(Name = "IconNumber")
  531. {
  532. this._.IconNumber := Value
  533. if(this._.Icon)
  534. this.SetIcon(this.Icon, Value)
  535. return Value
  536. }
  537. }
  538. }
  539. }
  540. }