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