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