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 }