1 /** DGui project file. 2 3 Copyright: Trogu Antonio Davide 2011-2013 4 5 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). 6 7 Authors: Trogu Antonio Davide 8 */ 9 module dgui.treeview; 10 11 import std.utf: toUTFz; 12 import dgui.core.utils; 13 import dgui.core.controls.subclassedcontrol; 14 import dgui.imagelist; 15 16 enum NodeInsertMode 17 { 18 head = TVI_FIRST, 19 tail = TVI_LAST, 20 } 21 22 class TreeNode: Handle!(HTREEITEM)//, IDisposable 23 { 24 private Collection!(TreeNode) _nodes; 25 private TreeView _owner; 26 private TreeNode _parent; 27 private NodeInsertMode _nim; 28 private bool _lazyNode; 29 private bool _childNodesCreated; 30 private string _text; 31 private int _imgIndex; 32 private int _selImgIndex; 33 34 mixin tagProperty; 35 36 package this(TreeView owner, string txt, int imgIndex, int selImgIndex, NodeInsertMode nim) 37 { 38 this._childNodesCreated = false; 39 this._owner = owner; 40 this._text = txt; 41 this._imgIndex = imgIndex; 42 this._selImgIndex = selImgIndex; 43 this._nim = nim; 44 } 45 46 package this(TreeView owner, TreeNode parent, string txt, int imgIndex, int selImgIndex, NodeInsertMode nim) 47 { 48 this._parent = parent; 49 this(owner, txt, imgIndex, selImgIndex, nim); 50 } 51 52 /* 53 public ~this() 54 { 55 this.dispose(); 56 } 57 58 public void dispose() 59 { 60 if(this._nodes) 61 { 62 this._nodes.clear(); 63 } 64 65 this._owner = null; 66 this._handle = null; 67 this._parent = null; 68 } 69 */ 70 71 public final TreeNode addNode(string txt, int imgIndex = -1, int selImgIndex = -1, NodeInsertMode nim = NodeInsertMode.tail) 72 { 73 if(!this._nodes) 74 { 75 this._nodes = new Collection!(TreeNode)(); 76 } 77 78 TreeNode tn = new TreeNode(this._owner, this, txt, imgIndex, selImgIndex == -1 ? imgIndex : selImgIndex, nim); 79 this._nodes.add(tn); 80 81 if(this._owner && this._owner.created) 82 { 83 TreeView.createTreeNode(tn); 84 } 85 86 return tn; 87 } 88 89 public final TreeNode addNode(string txt, int imgIndex, NodeInsertMode nim) 90 { 91 return this.addNode(txt, imgIndex, imgIndex, nim); 92 } 93 94 public final TreeNode addNode(string txt, NodeInsertMode nim) 95 { 96 return this.addNode(txt, -1, -1, nim); 97 } 98 99 public final void removeNode(TreeNode node) 100 { 101 if(this.created) 102 { 103 TreeView.removeTreeNode(node); 104 } 105 106 if(this._nodes) 107 { 108 this._nodes.remove(node); 109 } 110 } 111 112 public final void removeNode(int idx) 113 { 114 TreeNode node = null; 115 116 if(this._nodes) 117 { 118 node = this._nodes[idx]; 119 } 120 121 if(node) 122 { 123 TreeView.removeTreeNode(node); 124 } 125 } 126 127 public final void remove() 128 { 129 TreeView.removeTreeNode(this); 130 } 131 132 @property package NodeInsertMode insertMode() 133 { 134 return this._nim; 135 } 136 137 @property public final TreeView treeView() 138 { 139 return this._owner; 140 } 141 142 @property public final TreeNode parent() 143 { 144 return this._parent; 145 } 146 147 @property public final bool selected() 148 { 149 if(this._owner && this._owner.created) 150 { 151 TVITEMW tvi = void; 152 153 tvi.mask = TVIF_STATE | TVIF_HANDLE; 154 tvi.hItem = this._handle; 155 tvi.stateMask = TVIS_SELECTED; 156 157 this._owner.sendMessage(TVM_GETITEMW, 0, cast(LPARAM)&tvi); 158 return (tvi.state & TVIS_SELECTED) ? true : false; 159 } 160 161 return false; 162 } 163 164 @property public final bool lazyNode() 165 { 166 return this._lazyNode; 167 } 168 169 @property public final void lazyNode(bool b) 170 { 171 this._lazyNode = b; 172 } 173 174 @property public final string text() 175 { 176 return this._text; 177 } 178 179 @property public final void text(string txt) 180 { 181 this._text = txt; 182 183 if(this._owner && this._owner.created) 184 { 185 TVITEMW tvi = void; 186 187 tvi.mask = TVIF_TEXT | TVIF_HANDLE; 188 tvi.hItem = this._handle; 189 tvi.pszText = toUTFz!(wchar*)(txt); 190 this._owner.sendMessage(TVM_SETITEMW, 0, cast(LPARAM)&tvi); 191 } 192 } 193 194 @property public final int imageIndex() 195 { 196 return this._imgIndex; 197 } 198 199 @property public final void imageIndex(int idx) 200 { 201 this._imgIndex = idx; 202 203 if(this._owner && this._owner.created) 204 { 205 TVITEMW tvi = void; 206 207 tvi.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_HANDLE; 208 tvi.hItem = this._handle; 209 this._owner.sendMessage(TVM_GETITEMW, 0, cast(LPARAM)&tvi); 210 211 if(tvi.iSelectedImage == tvi.iImage) //Non e' mai stata assegnata veramente, quindi SelectedImage = Image. 212 { 213 tvi.iSelectedImage = idx; 214 } 215 216 tvi.iImage = idx; 217 this._owner.sendMessage(TVM_SETITEMW, 0, cast(LPARAM)&tvi); 218 } 219 } 220 221 @property public final int selectedImageIndex() 222 { 223 return this._selImgIndex; 224 } 225 226 @property public final void selectedImageIndex(int idx) 227 { 228 this._selImgIndex = idx; 229 230 if(this._owner && this._owner.created) 231 { 232 TVITEMW tvi = void; 233 234 tvi.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_HANDLE; 235 tvi.hItem = this._handle; 236 this._owner.sendMessage(TVM_GETITEMW, 0, cast(LPARAM)&tvi); 237 238 idx == -1 ? (tvi.iSelectedImage = tvi.iImage) : (tvi.iSelectedImage = idx); 239 this._owner.sendMessage(TVM_SETITEMW, 0, cast(LPARAM)&tvi); 240 } 241 } 242 243 @property public final TreeNode[] nodes() 244 { 245 if(this._nodes) 246 { 247 return this._nodes.get(); 248 } 249 250 return null; 251 } 252 253 public final void collapse() 254 { 255 if(this._owner && this._owner.createCanvas() && this.created) 256 { 257 this._owner.sendMessage(TVM_EXPAND, TVE_COLLAPSE, cast(LPARAM)this._handle); 258 } 259 } 260 261 public final void expand() 262 { 263 if(this._owner && this._owner.createCanvas() && this.created) 264 { 265 this._owner.sendMessage(TVM_EXPAND, TVE_EXPAND, cast(LPARAM)this._handle); 266 } 267 } 268 269 @property public final bool hasNodes() 270 { 271 return (this._nodes ? true : false); 272 } 273 274 @property public final int index() 275 { 276 if(this._parent && this._parent.hasNodes) 277 { 278 int i = 0; 279 280 foreach(TreeNode node; this._parent.nodes) 281 { 282 if(node is this) 283 { 284 return i; 285 } 286 287 i++; 288 } 289 } 290 291 return -1; 292 } 293 294 @property public override HTREEITEM handle() 295 { 296 return super.handle(); 297 } 298 299 @property package void handle(HTREEITEM hTreeNode) 300 { 301 this._handle = hTreeNode; 302 } 303 304 package void doChildNodes() 305 { 306 if(this._nodes && !this._childNodesCreated) 307 { 308 foreach(TreeNode tn; this._nodes) 309 { 310 if(!tn.created) 311 { 312 TreeView.createTreeNode(tn); 313 } 314 } 315 316 this._childNodesCreated = true; 317 } 318 } 319 } 320 321 public alias ItemChangedEventArgs!(TreeNode) TreeNodeChangedEventArgs; 322 public alias ItemEventArgs!(TreeNode) TreeNodeEventArgs; 323 public alias CancelEventArgs!(TreeNode) CancelTreeNodeEventArgs; 324 325 class TreeView: SubclassedControl 326 { 327 public Event!(Control, CancelTreeNodeEventArgs) selectedNodeChanging; 328 public Event!(Control, TreeNodeChangedEventArgs) selectedNodeChanged; 329 public Event!(Control, CancelTreeNodeEventArgs) treeNodeExpanding; 330 public Event!(Control, TreeNodeEventArgs) treeNodeExpanded; 331 public Event!(Control, TreeNodeEventArgs) treeNodeCollapsed; 332 333 private Collection!(TreeNode) _nodes; 334 private ImageList _imgList; 335 private TreeNode _selectedNode; 336 337 public final void clear() 338 { 339 this.sendMessage(TVM_DELETEITEM, 0, cast(LPARAM)TVI_ROOT); 340 341 if(this._nodes) 342 { 343 this._nodes.clear(); 344 } 345 } 346 347 public final TreeNode addNode(string txt, int imgIndex = -1, int selImgIndex = -1, NodeInsertMode nim = NodeInsertMode.tail) 348 { 349 if(!this._nodes) 350 { 351 this._nodes = new Collection!(TreeNode)(); 352 } 353 354 TreeNode tn = new TreeNode(this, txt, imgIndex, selImgIndex == -1 ? imgIndex : selImgIndex, nim); 355 this._nodes.add(tn); 356 357 if(this.created) 358 { 359 TreeView.createTreeNode(tn); 360 } 361 362 return tn; 363 } 364 365 public final TreeNode addNode(string txt, int imgIndex, NodeInsertMode nim) 366 { 367 return this.addNode(txt, imgIndex, imgIndex, nim); 368 } 369 370 public final TreeNode addNode(string txt, NodeInsertMode nim) 371 { 372 return this.addNode(txt, -1, -1, nim); 373 } 374 375 public final void removeNode(TreeNode node) 376 { 377 if(this.created) 378 { 379 TreeView.removeTreeNode(node); 380 } 381 382 if(this._nodes) 383 { 384 this._nodes.remove(node); 385 } 386 } 387 388 public final void removeNode(int idx) 389 { 390 TreeNode node = null; 391 392 if(this._nodes) 393 { 394 node = this._nodes[idx]; 395 } 396 397 if(node) 398 { 399 this.removeTreeNode(node); 400 } 401 } 402 403 @property public final Collection!(TreeNode) nodes() 404 { 405 return this._nodes; 406 } 407 408 @property public final ImageList imageList() 409 { 410 return this._imgList; 411 } 412 413 @property public final void imageList(ImageList imgList) 414 { 415 this._imgList = imgList; 416 417 if(this.created) 418 { 419 this.sendMessage(TVM_SETIMAGELIST, TVSIL_NORMAL, cast(LPARAM)this._imgList.handle); 420 } 421 } 422 423 @property public final TreeNode selectedNode() 424 { 425 return this._selectedNode; 426 } 427 428 @property public final void selectedNode(TreeNode node) 429 { 430 this._selectedNode = node; 431 432 if(this.created) 433 { 434 this.sendMessage(TVM_SELECTITEM, TVGN_FIRSTVISIBLE, cast(LPARAM)node.handle); 435 } 436 } 437 438 public final void collapse() 439 { 440 if(this.created) 441 { 442 this.sendMessage(TVM_EXPAND, TVE_COLLAPSE, cast(LPARAM)TVI_ROOT); 443 } 444 } 445 446 public final void expand() 447 { 448 if(this.created) 449 { 450 this.sendMessage(TVM_EXPAND, TVE_EXPAND, cast(LPARAM)TVI_ROOT); 451 } 452 } 453 454 package static void createTreeNode(TreeNode node) 455 { 456 TVINSERTSTRUCTW tvis; 457 458 tvis.hParent = (node.parent ? node.parent.handle : cast(HTREEITEM)TVI_ROOT); 459 tvis.hInsertAfter = cast(HTREEITEM)node.insertMode; 460 tvis.item.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_CHILDREN | TVIF_TEXT | TVIF_PARAM; 461 tvis.item.cChildren = I_CHILDRENCALLBACK; 462 tvis.item.iImage = node.imageIndex; 463 tvis.item.iSelectedImage = node.selectedImageIndex; 464 tvis.item.pszText = toUTFz!(wchar*)(node.text); 465 tvis.item.lParam = winCast!(LPARAM)(node); 466 467 TreeView tvw = node.treeView; 468 node.handle = cast(HTREEITEM)tvw.sendMessage(TVM_INSERTITEMW, 0, cast(LPARAM)&tvis); 469 470 /* 471 * Performance Killer: simulate a virtual tree view instead 472 * 473 if(node.hasNodes) 474 { 475 node.doChildNodes(); 476 } 477 */ 478 479 //tvw.redraw(); 480 } 481 482 package static void removeTreeNode(TreeNode node) 483 { 484 node.treeView.sendMessage(TVM_DELETEITEM, 0, cast(LPARAM)node.handle); 485 //node.dispose(); 486 } 487 488 protected override void createControlParams(ref CreateControlParams ccp) 489 { 490 ccp.superclassName = WC_TREEVIEW; 491 ccp.className = WC_DTREEVIEW; 492 this.setStyle(TVS_LINESATROOT | TVS_HASLINES | TVS_HASBUTTONS, true); 493 ccp.defaultBackColor = SystemColors.colorWindow; 494 495 // Tree view is Double buffered in DGui 496 TreeView.setBit(this._cBits, ControlBits.doubleBuffered, true); 497 super.createControlParams(ccp); 498 } 499 500 protected override void onHandleCreated(EventArgs e) 501 { 502 if(this._imgList) 503 { 504 this.sendMessage(TVM_SETIMAGELIST, TVSIL_NORMAL, cast(LPARAM)this._imgList.handle); 505 } 506 507 if(this._nodes) 508 { 509 foreach(TreeNode tn; this._nodes) 510 { 511 TreeView.createTreeNode(tn); 512 } 513 } 514 515 super.onHandleCreated(e); 516 } 517 518 protected override void onReflectedMessage(ref Message m) 519 { 520 if(m.msg == WM_NOTIFY) 521 { 522 NMTREEVIEWW* pNotifyTreeView = cast(NMTREEVIEWW*)m.lParam; 523 524 switch(pNotifyTreeView.hdr.code) 525 { 526 case TVN_GETDISPINFOW: 527 { 528 NMTVDISPINFOW* pTvDispInfo = cast(NMTVDISPINFOW*)m.lParam; 529 TreeNode node = winCast!(TreeNode)(pTvDispInfo.item.lParam); 530 531 if(node.lazyNode || node.nodes) //Is a Lazy Node, or has childNodes sooner or later a child node will be added 532 { 533 pTvDispInfo.item.cChildren = node.nodes ? node.nodes.length : 1; 534 } 535 else 536 { 537 pTvDispInfo.item.cChildren = 0; 538 } 539 } 540 break; 541 542 case TVN_ITEMEXPANDINGW: 543 { 544 TreeNode node = winCast!(TreeNode)(pNotifyTreeView.itemNew.lParam); 545 scope CancelTreeNodeEventArgs e = new CancelTreeNodeEventArgs(node); 546 547 this.onTreeNodeExpanding(e); //Allow the user to add nodes if e.cancel is 'false' 548 549 if(!e.cancel && pNotifyTreeView.action & TVE_EXPAND) 550 { 551 node.doChildNodes(); 552 } 553 554 m.result = e.cancel; 555 } 556 break; 557 558 case TVN_ITEMEXPANDEDW: 559 { 560 TreeNode node = winCast!(TreeNode)(pNotifyTreeView.itemNew.lParam); 561 scope TreeNodeEventArgs e = new TreeNodeEventArgs(node); 562 563 if(pNotifyTreeView.action & TVE_EXPAND) 564 { 565 this.onTreeNodeExpanded(e); 566 } 567 else if(pNotifyTreeView.action & TVE_COLLAPSE) 568 { 569 this.onTreeNodeCollapsed(e); 570 } 571 } 572 break; 573 574 case TVN_SELCHANGINGW: 575 { 576 TreeNode node = winCast!(TreeNode)(pNotifyTreeView.itemNew.lParam); 577 scope CancelTreeNodeEventArgs e = new CancelTreeNodeEventArgs(node); 578 this.onSelectedNodeChanging(e); 579 m.result = e.cancel; 580 } 581 break; 582 583 case TVN_SELCHANGEDW: 584 { 585 TreeNode oldNode = winCast!(TreeNode)(pNotifyTreeView.itemOld.lParam); 586 TreeNode newNode = winCast!(TreeNode)(pNotifyTreeView.itemNew.lParam); 587 588 this._selectedNode = newNode; 589 scope TreeNodeChangedEventArgs e = new TreeNodeChangedEventArgs(oldNode, newNode); 590 this.onSelectedNodeChanged(e); 591 } 592 break; 593 594 case NM_RCLICK: //Trigger a WM_CONTEXMENU Message (Fixes the double click/context menu bug, probably it's a windows bug). 595 Message sm = Message(this._handle, WM_CONTEXTMENU, 0, 0); 596 this.wndProc(sm); 597 m.result = sm.result; 598 break; 599 600 default: 601 super.onReflectedMessage(m); 602 break; 603 } 604 } 605 } 606 607 protected void onTreeNodeExpanding(CancelTreeNodeEventArgs e) 608 { 609 this.treeNodeExpanding(this, e); 610 } 611 612 protected void onTreeNodeExpanded(TreeNodeEventArgs e) 613 { 614 this.treeNodeExpanded(this, e); 615 } 616 617 protected void onTreeNodeCollapsed(TreeNodeEventArgs e) 618 { 619 this.treeNodeCollapsed(this, e); 620 } 621 622 protected void onSelectedNodeChanging(CancelTreeNodeEventArgs e) 623 { 624 this.selectedNodeChanging(this, e); 625 } 626 627 protected void onSelectedNodeChanged(TreeNodeChangedEventArgs e) 628 { 629 this.selectedNodeChanged(this, e); 630 } 631 }