1 // Written by Christopher E. Miller
2 // See the included license.txt for copyright and license details.
3 
4 
5 
6 module dfl.listview;
7 
8 private import dfl.internal.dlib, dfl.internal.clib;
9 
10 private import dfl.base, dfl.control, dfl.internal.winapi, dfl.application;
11 private import dfl.event, dfl.drawing, dfl.collections, dfl.internal.utf;
12 
13 version(DFL_NO_IMAGELIST) {
14 }
15 else {
16    private import dfl.imagelist;
17 }
18 
19 
20 private extern(Windows) void _initListview();
21 
22 
23 
24 enum ListViewAlignment: ubyte {
25    TOP, ///
26    DEFAULT, /// ditto
27    LEFT, /// ditto
28    SNAP_TO_GRID, /// ditto
29 }
30 
31 
32 private union CallText {
33    Dstringz ansi;
34    Dwstringz unicode;
35 }
36 
37 
38 private CallText getCallText(Dstring text) {
39    CallText result;
40    if(text is null) {
41       if(useUnicode) {
42          result.unicode = null;
43       } else {
44          result.ansi = null;
45       }
46    } else {
47       if(useUnicode) {
48          result.unicode = toUnicodez(text);
49       } else {
50          result.ansi = toAnsiz(text);
51       }
52    }
53    return result;
54 }
55 
56 
57 package union LvColumn {
58    LV_COLUMNW lvcw;
59    LV_COLUMNA lvca;
60    struct {
61       UINT mask;
62       int fmt;
63       int cx;
64       private void* pszText;
65       int cchTextMax;
66       int iSubItem;
67    }
68 }
69 
70 
71 
72 class ListViewSubItem: DObject {
73 
74    this() {
75       Application.ppin(cast(void*)this);
76    }
77 
78    /// ditto
79    this(Dstring thisSubItemText) {
80       this();
81 
82       settextin(thisSubItemText);
83    }
84 
85    /// ditto
86    this(ListViewItem owner, Dstring thisSubItemText) {
87       this();
88 
89       settextin(thisSubItemText);
90       if(owner) {
91          this._item = owner;
92          owner.subItems.add(this);
93       }
94    }
95 
96    /+
97    this(Object obj) { // package
98       this(getObjectString(obj));
99    }
100    +/
101 
102 
103    package final void settextin(Dstring newText) {
104       calltxt = getCallText(newText);
105       _txt = newText;
106    }
107 
108 
109    override Dstring toString() {
110       return text;
111    }
112 
113 
114    override Dequ opEquals(Object o) {
115       return text == getObjectString(o);
116    }
117 
118 
119    Dequ opEquals(Dstring val) {
120       return text == val;
121    }
122 
123 
124    override int opCmp(Object o) {
125       return stringICmp(text, getObjectString(o));
126    }
127 
128 
129    int opCmp(Dstring val) {
130       return stringICmp(text, val);
131    }
132 
133 
134 
135    final @property void text(Dstring newText) { // setter
136       settextin(newText);
137 
138       if(_item && _item.lview && _item.lview.created) {
139          int ii, subi;
140          ii = _item.lview.items.indexOf(_item);
141          assert(-1 != ii);
142          subi = _item.subItems.indexOf(this);
143          assert(-1 != subi);
144          _item.lview.updateItemText(ii, newText, subi + 1); // Sub items really start at 1 in the list view.
145       }
146    }
147 
148    /// ditto
149    final @property Dstring text() { // getter
150       return _txt;
151    }
152 
153 
154  private:
155    package ListViewItem _item;
156    Dstring _txt;
157    package CallText calltxt;
158 }
159 
160 
161 
162 class ListViewItem: DObject {
163 
164    static class ListViewSubItemCollection {
165       protected this(ListViewItem owner)
166       in {
167          assert(!owner.isubs);
168       }
169       body {
170          _item = owner;
171       }
172 
173 
174     private:
175 
176       ListViewItem _item;
177       package ListViewSubItem[] _subs;
178 
179 
180       void _adding(size_t idx, ListViewSubItem val) {
181          if(val._item) {
182             throw new DflException("ListViewSubItem already belongs to a ListViewItem");
183          }
184       }
185 
186 
187     public:
188 
189       mixin ListWrapArray!(ListViewSubItem, _subs,
190                            _adding, _blankListCallback!(ListViewSubItem),
191                            _blankListCallback!(ListViewSubItem), _blankListCallback!(ListViewSubItem),
192                            true, false, false);
193    }
194 
195 
196 
197    this() {
198       Application.ppin(cast(void*)this);
199 
200       isubs = new ListViewSubItemCollection(this);
201    }
202 
203    /// ditto
204    this(Dstring text) {
205       this();
206 
207       settextin(text);
208    }
209 
210 
211    private final void _setcheckstate(int thisindex, bool bchecked) {
212       if(lview && lview.created) {
213          LV_ITEMA li;
214          li.stateMask = LVIS_STATEIMAGEMASK;
215          li.state = cast(LPARAM)(bchecked ? 2 : 1) << 12;
216          lview.prevwproc(LVM_SETITEMSTATE, cast(WPARAM)thisindex, cast(LPARAM)&li);
217       }
218    }
219 
220 
221    private final bool _getcheckstate(int thisindex) {
222       if(lview && lview.created) {
223          if((lview.prevwproc(LVM_GETITEMSTATE, cast(WPARAM)thisindex, LVIS_STATEIMAGEMASK) >> 12) - 1) {
224             return true;
225          }
226       }
227       return false;
228    }
229 
230 
231 
232    final @property void checked(bool byes) { // setter
233       return _setcheckstate(index, byes);
234    }
235 
236    /// ditto
237    final @property bool checked() { // getter
238       return _getcheckstate(index);
239    }
240 
241 
242    package final void settextin(Dstring newText) {
243       calltxt = getCallText(newText);
244       _txt = newText;
245    }
246 
247 
248    override Dstring toString() {
249       return text;
250    }
251 
252 
253    override Dequ opEquals(Object o) {
254       return text == getObjectString(o);
255    }
256 
257 
258    Dequ opEquals(Dstring val) {
259       return text == val;
260    }
261 
262 
263    override int opCmp(Object o) {
264       return stringICmp(text, getObjectString(o));
265    }
266 
267 
268    int opCmp(Dstring val) {
269       return stringICmp(text, val);
270    }
271 
272 
273 
274    final @property Rect bounds() { // getter
275       if(lview) {
276          int i = index;
277          assert(-1 != i);
278          return lview.getItemRect(i);
279       }
280       return Rect(0, 0, 0, 0);
281    }
282 
283 
284 
285    final @property int index() { // getter
286       if(lview) {
287          return lview.litems.indexOf(this);
288       }
289       return -1;
290    }
291 
292 
293 
294    final @property void text(Dstring newText) { // setter
295       settextin(newText);
296 
297       if(lview && lview.created) {
298          lview.updateItemText(this, newText);
299       }
300    }
301 
302    /// ditto
303    final @property Dstring text() { // getter
304       return _txt;
305    }
306 
307 
308 
309    final @property void selected(bool byes) { // setter
310       if(lview && lview.created) {
311          LV_ITEMA li;
312          li.stateMask = LVIS_SELECTED;
313          if(byes) {
314             li.state = LVIS_SELECTED;
315          }
316          lview.prevwproc(LVM_SETITEMSTATE, cast(WPARAM)index, cast(LPARAM)&li);
317       }
318    }
319 
320    /// ditto
321    final @property bool selected() { // getter
322       if(lview && lview.created) {
323          if(lview.prevwproc(LVM_GETITEMSTATE, cast(WPARAM)index, LVIS_SELECTED)) {
324             return true;
325          }
326       }
327       return false;
328    }
329 
330 
331 
332    final @property ListView listView() { // getter
333       return lview;
334    }
335 
336 
337 
338    final @property void tag(Object obj) { // setter
339       _tag = obj;
340    }
341 
342    /// ditto
343    final @property Object tag() { // getter
344       return _tag;
345    }
346 
347 
348    final void beginEdit() {
349       if(lview && lview.created) {
350          if(dfl.internal.utf.useUnicode) {
351             lview.prevwproc(LVM_EDITLABELW, index, 0);
352          } else {
353             lview.prevwproc(LVM_EDITLABELA, index, 0);
354          }
355       }
356    }
357 
358 
359 
360    final @property ListViewSubItemCollection subItems() { // getter
361       return isubs;
362    }
363 
364 
365    version(DFL_NO_IMAGELIST) {
366    }
367    else {
368 
369       final @property void imageIndex(int index) { // setter
370          this._imgidx = index;
371 
372          if(lview && lview.created) {
373             lview.updateItem(this);
374          }
375       }
376 
377       /// ditto
378       final @property int imageIndex() { // getter
379          return _imgidx;
380       }
381    }
382 
383 
384  private:
385    package ListView lview = null;
386    Object _tag = null;
387    package ListViewSubItemCollection isubs = null;
388    version(DFL_NO_IMAGELIST) {
389    }
390    else {
391       int _imgidx = -1;
392    }
393    Dstring _txt;
394    package CallText calltxt;
395 }
396 
397 
398 
399 class ColumnHeader: DObject {
400 
401    this(Dstring text) {
402       this();
403 
404       this._txt = text;
405    }
406 
407    /// ditto
408    this() {
409       Application.ppin(cast(void*)this);
410    }
411 
412 
413 
414    final @property ListView listView() { // getter
415       return lview;
416    }
417 
418 
419 
420    final @property void text(Dstring newText) { // setter
421       _txt = newText;
422 
423       if(lview && lview.created) {
424          lview.updateColumnText(this, newText);
425       }
426    }
427 
428    /// ditto
429    final @property Dstring text() { // getter
430       return _txt;
431    }
432 
433 
434    override Dstring toString() {
435       return text;
436    }
437 
438 
439    override Dequ opEquals(Object o) {
440       return text == getObjectString(o);
441    }
442 
443 
444    Dequ opEquals(Dstring val) {
445       return text == val;
446    }
447 
448 
449    override int opCmp(Object o) {
450       return stringICmp(text, getObjectString(o));
451    }
452 
453 
454    int opCmp(Dstring val) {
455       return stringICmp(text, val);
456    }
457 
458 
459 
460    final @property int index() { // getter
461       if(lview) {
462          lview.cols.indexOf(this);
463       }
464       return -1;
465    }
466 
467 
468 
469    final @property void textAlign(HorizontalAlignment halign) { // setter
470       _align = halign;
471 
472       if(lview && lview.created) {
473          lview.updateColumnAlign(this, halign);
474       }
475    }
476 
477    /// ditto
478    final @property HorizontalAlignment textAlign() { // getter
479       return _align;
480    }
481 
482 
483 
484    final @property void width(int w) { // setter
485       _width = w;
486 
487       if(lview && lview.created) {
488          lview.updateColumnWidth(this, w);
489       }
490    }
491 
492    /// ditto
493    final @property int width() { // getter
494       if(lview && lview.created) {
495          int xx;
496          xx = lview.getColumnWidth(this);
497          if(-1 != xx) {
498             _width = xx;
499          }
500       }
501       return _width;
502    }
503 
504 
505  private:
506    package ListView lview;
507    Dstring _txt;
508    int _width;
509    HorizontalAlignment _align;
510 }
511 
512 
513 
514 class LabelEditEventArgs: EventArgs {
515 
516    this(ListViewItem item, Dstring label) {
517       _item = item;
518       _label = label;
519    }
520 
521    /// ditto
522    this(ListViewItem node) {
523       _item = item;
524    }
525 
526 
527 
528    final @property ListViewItem item() { // getter
529       return _item;
530    }
531 
532 
533 
534    final @property Dstring label() { // getter
535       return _label;
536    }
537 
538 
539 
540    final @property void cancelEdit(bool byes) { // setter
541       _cancel = byes;
542    }
543 
544    /// ditto
545    final @property bool cancelEdit() { // getter
546       return _cancel;
547    }
548 
549 
550  private:
551    ListViewItem _item;
552    Dstring _label;
553    bool _cancel = false;
554 }
555 
556 
557 /+
558 class ItemCheckEventArgs: EventArgs {
559    this(int index, CheckState newCheckState, CheckState oldCheckState) {
560       this._idx = index;
561       this._ncs = newCheckState;
562       this._ocs = oldCheckState;
563    }
564 
565 
566    final @property CheckState currentValue() { // getter
567       return _ocs;
568    }
569 
570 
571    /+
572    final @property void newValue(CheckState cs) { // setter
573       _ncs = cs;
574    }
575    +/
576 
577 
578    final @property CheckState newValue() { // getter
579       return _ncs;
580    }
581 
582 
583  private:
584    int _idx;
585    CheckState _ncs, _ocs;
586 }
587 +/
588 
589 
590 class ItemCheckedEventArgs: EventArgs {
591    this(ListViewItem item) {
592       this._item = item;
593    }
594 
595 
596    final @property ListViewItem item() { // getter
597       return this._item;
598    }
599 
600 
601  private:
602    ListViewItem _item;
603 }
604 
605 
606 
607 class ListView: ControlSuperClass { // docmain
608 
609    static class ListViewItemCollection {
610       protected this(ListView lv)
611       in {
612          assert(lv.litems is null);
613       }
614       body {
615          this.lv = lv;
616       }
617 
618 
619       void add(ListViewItem item) {
620          int ii = -1; // Insert index.
621 
622          switch(lv.sorting) {
623             case SortOrder.NONE: // Add to end.
624                ii = _items.length;
625                break;
626 
627             case SortOrder.ASCENDING: // Insertion sort.
628                for(ii = 0; ii != _items.length; ii++) {
629                   assert(lv._sortproc);
630                   //if(item < _items[ii])
631                   if(lv._sortproc(item, _items[ii]) < 0) {
632                      break;
633                   }
634                }
635                break;
636 
637             case SortOrder.DESCENDING: // Insertion sort.
638                for(ii = 0; ii != _items.length; ii++) {
639                   assert(lv._sortproc);
640                   //if(item >= _items[ii])
641                   if(lv._sortproc(item, _items[ii]) >= 0) {
642                      break;
643                   }
644                }
645                break;
646 
647             default:
648                assert(0);
649          }
650 
651          assert(-1 != ii);
652          insert(ii, item);
653       }
654 
655       void add(Dstring text) {
656          return add(new ListViewItem(text));
657       }
658 
659 
660       // addRange must have special case in case of sorting.
661 
662       void addRange(ListViewItem[] range) {
663          foreach(ListViewItem item; range) {
664             add(item);
665          }
666       }
667 
668       /+
669       void addRange(Object[] range) {
670          foreach(Object o; range) {
671             add(o);
672          }
673       }
674       +/
675 
676       void addRange(Dstring[] range) {
677          foreach(Dstring s; range) {
678             add(s);
679          }
680       }
681 
682 
683     private:
684 
685       ListView lv;
686       package ListViewItem[] _items;
687 
688 
689       package final @property bool created() { // getter
690          return lv && lv.created();
691       }
692 
693 
694       package final void doListItems() // DMD 0.125: this member is not accessible when private.
695       in {
696          assert(created);
697       }
698       body {
699          int ii;
700          foreach(int i, ListViewItem item; _items) {
701             ii = lv._ins(i, item);
702             //assert(-1 != ii);
703             assert(i == ii);
704 
705             /+
706             // Add sub items.
707             foreach(int subi, ListViewSubItem subItem; item.isubs._subs) {
708                lv._ins(i, subItem, subi + 1); // Sub items really start at 1 in the list view.
709             }
710             +/
711          }
712       }
713 
714 
715       void verifyNoParent(ListViewItem item) {
716          if(item.lview) {
717             throw new DflException("ListViewItem already belongs to a ListView");
718          }
719       }
720 
721 
722       void _adding(size_t idx, ListViewItem val) {
723          verifyNoParent(val);
724       }
725 
726 
727       void _added(size_t idx, ListViewItem val) {
728          val.lview = lv;
729 
730          int i;
731          if(created) {
732             i = lv._ins(idx, val);
733             assert(-1 != i);
734          }
735       }
736 
737 
738       void _removed(size_t idx, ListViewItem val) {
739          if(size_t.max == idx) { // Clear all.
740             if(created) {
741                lv.prevwproc(LVM_DELETEALLITEMS, 0, 0);
742             }
743          } else {
744             if(created) {
745                lv.prevwproc(LVM_DELETEITEM, cast(WPARAM)idx, 0);
746             }
747          }
748       }
749 
750 
751     public:
752 
753       mixin ListWrapArray!(ListViewItem, _items,
754                            _adding, _added,
755                            _blankListCallback!(ListViewItem), _removed,
756                            true, false, false);
757    }
758 
759 
760 
761    static class ColumnHeaderCollection {
762       protected this(ListView owner)
763       in {
764          assert(!owner.cols);
765       }
766       body {
767          lv = owner;
768       }
769 
770 
771     private:
772       ListView lv;
773       ColumnHeader[] _headers;
774 
775 
776       package final @property bool created() { // getter
777          return lv && lv.created();
778       }
779 
780 
781       void verifyNoParent(ColumnHeader header) {
782          if(header.lview) {
783             throw new DflException("ColumnHeader already belongs to a ListView");
784          }
785       }
786 
787 
788       package final void doListHeaders() // DMD 0.125: this member is not accessible when private.
789       in {
790          assert(created);
791       }
792       body {
793          int ii;
794          foreach(int i, ColumnHeader header; _headers) {
795             ii = lv._ins(i, header);
796             assert(-1 != ii);
797             //assert(i == ii);
798          }
799       }
800 
801 
802       void _adding(size_t idx, ColumnHeader val) {
803          verifyNoParent(val);
804       }
805 
806 
807       void _added(size_t idx, ColumnHeader val) {
808          val.lview = lv;
809 
810          int i;
811          if(created) {
812             i = lv._ins(idx, val);
813             assert(-1 != i);
814          }
815       }
816 
817 
818       void _removed(size_t idx, ColumnHeader val) {
819          if(size_t.max == idx) { // Clear all.
820          } else {
821             if(created) {
822                lv.prevwproc(LVM_DELETECOLUMN, cast(WPARAM)idx, 0);
823             }
824          }
825       }
826 
827 
828     public:
829 
830       mixin ListWrapArray!(ColumnHeader, _headers,
831                            _adding, _added,
832                            _blankListCallback!(ColumnHeader), _removed,
833                            true, false, false,
834                            true); // CLEAR_EACH
835    }
836 
837 
838 
839    static class SelectedIndexCollection {
840       deprecated alias length count;
841 
842       @property int length() { // getter
843          if(!lview.created) {
844             return 0;
845          }
846 
847          int result = 0;
848          foreach(int onidx; this) {
849             result++;
850          }
851          return result;
852       }
853 
854 
855       int opIndex(int idx) {
856          foreach(int onidx; this) {
857             if(!idx) {
858                return onidx;
859             }
860             idx--;
861          }
862 
863          // If it's not found it's out of bounds and bad things happen.
864          assert(0);
865       }
866 
867 
868       bool contains(int idx) {
869          return indexOf(idx) != -1;
870       }
871 
872 
873       int indexOf(int idx) {
874          int i = 0;
875          foreach(int onidx; this) {
876             if(onidx == idx) {
877                return i;
878             }
879             i++;
880          }
881          return -1;
882       }
883 
884 
885       int opApply(int delegate(ref int) dg) {
886          if(!lview.created) {
887             return 0;
888          }
889 
890          int result = 0;
891          int idx = -1;
892          for(;;) {
893             idx = cast(int)lview.prevwproc(LVM_GETNEXTITEM, cast(WPARAM)idx, MAKELPARAM(cast(UINT)LVNI_SELECTED, 0));
894             if(-1 == idx) { // Done.
895                break;
896             }
897             int dgidx = idx; // Prevent ref.
898             result = dg(dgidx);
899             if(result) {
900                break;
901             }
902          }
903          return result;
904       }
905 
906       mixin OpApplyAddIndex!(opApply, int);
907 
908 
909       protected this(ListView lv) {
910          lview = lv;
911       }
912 
913 
914     package:
915       ListView lview;
916    }
917 
918 
919    deprecated alias SelectedItemCollection SelectedListViewItemCollection;
920 
921 
922    static class SelectedItemCollection {
923       deprecated alias length count;
924 
925       @property int length() { // getter
926          if(!lview.created) {
927             return 0;
928          }
929 
930          int result = 0;
931          foreach(ListViewItem onitem; this) {
932             result++;
933          }
934          return result;
935       }
936 
937 
938       ListViewItem opIndex(int idx) {
939          foreach(ListViewItem onitem; this) {
940             if(!idx) {
941                return onitem;
942             }
943             idx--;
944          }
945 
946          // If it's not found it's out of bounds and bad things happen.
947          assert(0);
948       }
949 
950 
951       bool contains(ListViewItem item) {
952          return indexOf(item) != -1;
953       }
954 
955 
956       int indexOf(ListViewItem item) {
957          int i = 0;
958          foreach(ListViewItem onitem; this) {
959             if(onitem == item) { // Not using is.
960                return i;
961             }
962             i++;
963          }
964          return -1;
965       }
966 
967 
968       int opApply(int delegate(ref ListViewItem) dg) {
969          if(!lview.created) {
970             return 0;
971          }
972 
973          int result = 0;
974          int idx = -1;
975          for(;;) {
976             idx = cast(int)lview.prevwproc(LVM_GETNEXTITEM, cast(WPARAM)idx, MAKELPARAM(cast(UINT)LVNI_SELECTED, 0));
977             if(-1 == idx) { // Done.
978                break;
979             }
980             ListViewItem litem = lview.litems._items[idx]; // Prevent ref.
981             result = dg(litem);
982             if(result) {
983                break;
984             }
985          }
986          return result;
987       }
988 
989       mixin OpApplyAddIndex!(opApply, ListViewItem);
990 
991 
992       protected this(ListView lv) {
993          lview = lv;
994       }
995 
996 
997     package:
998       ListView lview;
999    }
1000 
1001 
1002 
1003    static class CheckedIndexCollection {
1004       deprecated alias length count;
1005 
1006       @property int length() { // getter
1007          if(!lview.created) {
1008             return 0;
1009          }
1010 
1011          int result = 0;
1012          foreach(int onidx; this) {
1013             result++;
1014          }
1015          return result;
1016       }
1017 
1018 
1019       int opIndex(int idx) {
1020          foreach(int onidx; this) {
1021             if(!idx) {
1022                return onidx;
1023             }
1024             idx--;
1025          }
1026 
1027          // If it's not found it's out of bounds and bad things happen.
1028          assert(0);
1029       }
1030 
1031 
1032       bool contains(int idx) {
1033          return indexOf(idx) != -1;
1034       }
1035 
1036 
1037       int indexOf(int idx) {
1038          int i = 0;
1039          foreach(int onidx; this) {
1040             if(onidx == idx) {
1041                return i;
1042             }
1043             i++;
1044          }
1045          return -1;
1046       }
1047 
1048 
1049       int opApply(int delegate(ref int) dg) {
1050          if(!lview.created) {
1051             return 0;
1052          }
1053 
1054          int result = 0;
1055          foreach(ref size_t i, ref ListViewItem lvitem; lview.items) {
1056             if(lvitem._getcheckstate(i)) {
1057                int dgidx = i; // Prevent ref.
1058                result = dg(dgidx);
1059                if(result) {
1060                   break;
1061                }
1062             }
1063          }
1064          return result;
1065       }
1066 
1067       mixin OpApplyAddIndex!(opApply, int);
1068 
1069 
1070       protected this(ListView lv) {
1071          lview = lv;
1072       }
1073 
1074 
1075     package:
1076       ListView lview;
1077    }
1078 
1079 
1080    this() {
1081       _initListview();
1082 
1083       litems = new ListViewItemCollection(this);
1084       cols = new ColumnHeaderCollection(this);
1085       selidxcollection = new SelectedIndexCollection(this);
1086       selobjcollection = new SelectedItemCollection(this);
1087       checkedis = new CheckedIndexCollection(this);
1088 
1089       wstyle |= WS_TABSTOP | LVS_ALIGNTOP | LVS_AUTOARRANGE | LVS_SHAREIMAGELISTS;
1090       wexstyle |= WS_EX_CLIENTEDGE;
1091       ctrlStyle |= ControlStyles.SELECTABLE;
1092       wclassStyle = listviewClassStyle;
1093    }
1094 
1095 
1096 
1097    final @property void activation(ItemActivation ia) { // setter
1098       switch(ia) {
1099          case ItemActivation.STANDARD:
1100             _lvexstyle(LVS_EX_ONECLICKACTIVATE | LVS_EX_TWOCLICKACTIVATE, 0);
1101             break;
1102 
1103          case ItemActivation.ONE_CLICK:
1104             _lvexstyle(LVS_EX_ONECLICKACTIVATE | LVS_EX_TWOCLICKACTIVATE, LVS_EX_ONECLICKACTIVATE);
1105             break;
1106 
1107          case ItemActivation.TWO_CLICK:
1108             _lvexstyle(LVS_EX_ONECLICKACTIVATE | LVS_EX_TWOCLICKACTIVATE, LVS_EX_TWOCLICKACTIVATE);
1109             break;
1110 
1111          default:
1112             assert(0);
1113       }
1114    }
1115 
1116    /// ditto
1117    final @property ItemActivation activation() { // getter
1118       DWORD lvex;
1119       lvex = _lvexstyle();
1120       if(lvex & LVS_EX_ONECLICKACTIVATE) {
1121          return ItemActivation.ONE_CLICK;
1122       }
1123       if(lvex & LVS_EX_TWOCLICKACTIVATE) {
1124          return ItemActivation.TWO_CLICK;
1125       }
1126       return ItemActivation.STANDARD;
1127    }
1128 
1129 
1130    /+
1131 
1132    final void alignment(ListViewAlignment lva) {
1133       // TODO
1134 
1135       switch(lva) {
1136          case ListViewAlignment.TOP:
1137             _style((_style() & ~(LVS_ALIGNLEFT | foo)) | LVS_ALIGNTOP);
1138             break;
1139 
1140          default:
1141             assert(0);
1142       }
1143    }
1144 
1145    /// ditto
1146    final @property ListViewAlignment alignment() { // getter
1147       // TODO
1148    }
1149    +/
1150 
1151 
1152 
1153    final @property void allowColumnReorder(bool byes) { // setter
1154       _lvexstyle(LVS_EX_HEADERDRAGDROP, byes ? LVS_EX_HEADERDRAGDROP : 0);
1155    }
1156 
1157    /// ditto
1158    final @property bool allowColumnReorder() { // getter
1159       return (_lvexstyle() & LVS_EX_HEADERDRAGDROP) == LVS_EX_HEADERDRAGDROP;
1160    }
1161 
1162 
1163 
1164    final @property void autoArrange(bool byes) { // setter
1165       if(byes) {
1166          _style(_style() | LVS_AUTOARRANGE);
1167       } else {
1168          _style(_style() & ~LVS_AUTOARRANGE);
1169       }
1170 
1171       //_crecreate(); // ?
1172    }
1173 
1174    /// ditto
1175    final @property bool autoArrange() { // getter
1176       return (_style() & LVS_AUTOARRANGE) == LVS_AUTOARRANGE;
1177    }
1178 
1179 
1180    override @property void backColor(Color c) { // setter
1181       if(created) {
1182          COLORREF cref;
1183          if(Color.empty == c) {
1184             cref = CLR_NONE;
1185          } else {
1186             cref = c.toRgb();
1187          }
1188          prevwproc(LVM_SETBKCOLOR, 0, cast(LPARAM)cref);
1189          prevwproc(LVM_SETTEXTBKCOLOR, 0, cast(LPARAM)cref);
1190       }
1191 
1192       super.backColor = c;
1193    }
1194 
1195 
1196    override @property Color backColor() { // getter
1197       if(Color.empty == backc) {
1198          return defaultBackColor;
1199       }
1200       return backc;
1201    }
1202 
1203 
1204 
1205    final @property void borderStyle(BorderStyle bs) { // setter
1206       final switch(bs) {
1207          case BorderStyle.FIXED_3D:
1208             _style(_style() & ~WS_BORDER);
1209             _exStyle(_exStyle() | WS_EX_CLIENTEDGE);
1210             break;
1211 
1212          case BorderStyle.FIXED_SINGLE:
1213             _exStyle(_exStyle() & ~WS_EX_CLIENTEDGE);
1214             _style(_style() | WS_BORDER);
1215             break;
1216 
1217          case BorderStyle.NONE:
1218             _style(_style() & ~WS_BORDER);
1219             _exStyle(_exStyle() & ~WS_EX_CLIENTEDGE);
1220             break;
1221       }
1222 
1223       if(created) {
1224          redrawEntire();
1225       }
1226    }
1227 
1228    /// ditto
1229    final @property BorderStyle borderStyle() { // getter
1230       if(_exStyle() & WS_EX_CLIENTEDGE) {
1231          return BorderStyle.FIXED_3D;
1232       } else if(_style() & WS_BORDER) {
1233          return BorderStyle.FIXED_SINGLE;
1234       }
1235       return BorderStyle.NONE;
1236    }
1237 
1238 
1239 
1240    final @property void checkBoxes(bool byes) { // setter
1241       _lvexstyle(LVS_EX_CHECKBOXES, byes ? LVS_EX_CHECKBOXES : 0);
1242    }
1243 
1244    /// ditto
1245    final @property bool checkBoxes() { // getter
1246       return (_lvexstyle() & LVS_EX_CHECKBOXES) == LVS_EX_CHECKBOXES;
1247    }
1248 
1249 
1250 
1251    // ListView.CheckedIndexCollection
1252    final @property CheckedIndexCollection checkedIndices() { // getter
1253       return checkedis;
1254    }
1255 
1256 
1257    /+
1258 
1259    // ListView.CheckedListViewItemCollection
1260    final @property CheckedListViewItemCollection checkedItems() { // getter
1261       // TODO
1262    }
1263    +/
1264 
1265 
1266 
1267    final @property ColumnHeaderCollection columns() { // getter
1268       return cols;
1269    }
1270 
1271 
1272 
1273    // Extra.
1274    final @property int focusedIndex() { // getter
1275       if(!created) {
1276          return -1;
1277       }
1278       return cast(int)prevwproc(LVM_GETNEXTITEM, cast(WPARAM)-1, MAKELPARAM(cast(UINT)LVNI_FOCUSED, 0));
1279    }
1280 
1281 
1282 
1283    final @property ListViewItem focusedItem() { // getter
1284       int i;
1285       i = focusedIndex;
1286       if(-1 == i) {
1287          return null;
1288       }
1289       return litems._items[i];
1290    }
1291 
1292 
1293    override @property void foreColor(Color c) { // setter
1294       if(created) {
1295          prevwproc(LVM_SETTEXTCOLOR, 0, cast(LPARAM)c.toRgb());
1296       }
1297 
1298       super.foreColor = c;
1299    }
1300 
1301 
1302    override @property Color foreColor() { // getter
1303       if(Color.empty == forec) {
1304          return defaultForeColor;
1305       }
1306       return forec;
1307    }
1308 
1309 
1310 
1311    final @property void fullRowSelect(bool byes) { // setter
1312       _lvexstyle(LVS_EX_FULLROWSELECT, byes ? LVS_EX_FULLROWSELECT : 0);
1313    }
1314 
1315    /// ditto
1316    final @property bool fullRowSelect() { // getter
1317       return (_lvexstyle() & LVS_EX_FULLROWSELECT) == LVS_EX_FULLROWSELECT;
1318    }
1319 
1320 
1321 
1322    final @property void gridLines(bool byes) { // setter
1323       _lvexstyle(LVS_EX_GRIDLINES, byes ? LVS_EX_GRIDLINES : 0);
1324    }
1325 
1326    /// ditto
1327    final @property bool gridLines() { // getter
1328       return (_lvexstyle() & LVS_EX_GRIDLINES) == LVS_EX_GRIDLINES;
1329    }
1330 
1331 
1332    /+
1333 
1334    final @property void headerStyle(ColumnHeaderStyle chs) { // setter
1335       // TODO: LVS_NOCOLUMNHEADER ... default is clickable.
1336    }
1337 
1338    /// ditto
1339    final @property ColumnHeaderStyle headerStyle() { // getter
1340       // TODO
1341    }
1342    +/
1343 
1344 
1345 
1346    final @property void hideSelection(bool byes) { // setter
1347       if(byes) {
1348          _style(_style() & ~LVS_SHOWSELALWAYS);
1349       } else {
1350          _style(_style() | LVS_SHOWSELALWAYS);
1351       }
1352    }
1353 
1354    /// ditto
1355    final @property bool hideSelection() { // getter
1356       return (_style() & LVS_SHOWSELALWAYS) != LVS_SHOWSELALWAYS;
1357    }
1358 
1359 
1360 
1361    final @property void hoverSelection(bool byes) { // setter
1362       _lvexstyle(LVS_EX_TRACKSELECT, byes ? LVS_EX_TRACKSELECT : 0);
1363    }
1364 
1365    /// ditto
1366    final @property bool hoverSelection() { // getter
1367       return (_lvexstyle() & LVS_EX_TRACKSELECT) == LVS_EX_TRACKSELECT;
1368    }
1369 
1370 
1371 
1372    final @property ListViewItemCollection items() { // getter
1373       return litems;
1374    }
1375 
1376 
1377 
1378    // Simple as addRow("item", "sub item1", "sub item2", "etc");
1379    // rowstrings[0] is the item and rowstrings[1 .. rowstrings.length] are its sub items.
1380    //final void addRow(Dstring[] rowstrings ...)
1381    final ListViewItem addRow(Dstring[] rowstrings ...) {
1382       if(rowstrings.length) {
1383          ListViewItem item;
1384          item = new ListViewItem(rowstrings[0]);
1385          if(rowstrings.length > 1) {
1386             item.subItems.addRange(rowstrings[1 .. rowstrings.length]);
1387          }
1388          items.add(item);
1389          return item;
1390       }
1391       assert(0);
1392    }
1393 
1394 
1395 
1396    final @property void labelEdit(bool byes) { // setter
1397       if(byes) {
1398          _style(_style() | LVS_EDITLABELS);
1399       } else {
1400          _style(_style() & ~LVS_EDITLABELS);
1401       }
1402    }
1403 
1404    /// ditto
1405    final @property bool labelEdit() { // getter
1406       return (_style() & LVS_EDITLABELS) == LVS_EDITLABELS;
1407    }
1408 
1409 
1410 
1411    final @property void labelWrap(bool byes) { // setter
1412       if(byes) {
1413          _style(_style() & ~LVS_NOLABELWRAP);
1414       } else {
1415          _style(_style() | LVS_NOLABELWRAP);
1416       }
1417    }
1418 
1419    /// ditto
1420    final @property bool labelWrap() { // getter
1421       return (_style() & LVS_NOLABELWRAP) != LVS_NOLABELWRAP;
1422    }
1423 
1424 
1425 
1426    final @property void multiSelect(bool byes) { // setter
1427       if(byes) {
1428          _style(_style() & ~LVS_SINGLESEL);
1429       } else {
1430          _style(_style() | LVS_SINGLESEL);
1431 
1432          if(selectedItems.length > 1) {
1433             selectedItems[0].selected = true;   // Clear all but first selected.
1434          }
1435       }
1436    }
1437 
1438    /// ditto
1439    final @property bool multiSelect() { // getter
1440       return (_style() & LVS_SINGLESEL) != LVS_SINGLESEL;
1441    }
1442 
1443 
1444 
1445    // Note: scrollable=false is not compatible with the list or details(report) styles(views).
1446    // See Knowledge Base Article Q137520.
1447    final @property void scrollable(bool byes) { // setter
1448       if(byes) {
1449          _style(_style() & ~LVS_NOSCROLL);
1450       } else {
1451          _style(_style() | LVS_NOSCROLL);
1452       }
1453 
1454       _crecreate();
1455    }
1456 
1457    /// ditto
1458    final @property bool scrollable() { // getter
1459       return (_style() & LVS_NOSCROLL) != LVS_NOSCROLL;
1460    }
1461 
1462 
1463 
1464    final @property SelectedIndexCollection selectedIndices() { // getter
1465       return selidxcollection;
1466    }
1467 
1468 
1469 
1470    final @property SelectedItemCollection selectedItems() { // getter
1471       return selobjcollection;
1472    }
1473 
1474 
1475 
1476    final @property void view(View v) { // setter
1477       switch(v) {
1478          case View.LARGE_ICON:
1479             _style(_style() & ~(LVS_SMALLICON | LVS_LIST | LVS_REPORT));
1480             break;
1481 
1482          case View.SMALL_ICON:
1483             _style((_style() & ~(LVS_LIST | LVS_REPORT)) | LVS_SMALLICON);
1484             break;
1485 
1486          case View.LIST:
1487             _style((_style() & ~(LVS_SMALLICON | LVS_REPORT)) | LVS_LIST);
1488             break;
1489 
1490          case View.DETAILS:
1491             _style((_style() & ~(LVS_SMALLICON | LVS_LIST)) | LVS_REPORT);
1492             break;
1493 
1494          default:
1495             assert(0);
1496       }
1497 
1498       if(created) {
1499          redrawEntire();
1500       }
1501    }
1502 
1503    /// ditto
1504    final @property View view() { // getter
1505       LONG st;
1506       st = _style();
1507       if(st & LVS_SMALLICON) {
1508          return View.SMALL_ICON;
1509       }
1510       if(st & LVS_LIST) {
1511          return View.LIST;
1512       }
1513       if(st & LVS_REPORT) {
1514          return View.DETAILS;
1515       }
1516       return View.LARGE_ICON;
1517    }
1518 
1519 
1520 
1521    final @property void sorting(SortOrder so) { // setter
1522       if(so == _sortorder) {
1523          return;
1524       }
1525 
1526       switch(so) {
1527          case SortOrder.NONE:
1528             _sortproc = null;
1529             break;
1530 
1531          case SortOrder.ASCENDING:
1532          case SortOrder.DESCENDING:
1533             if(!_sortproc) {
1534                _sortproc = &_defsortproc;
1535             }
1536             break;
1537 
1538          default:
1539             assert(0);
1540       }
1541 
1542       _sortorder = so;
1543 
1544       sort();
1545    }
1546 
1547    /// ditto
1548    final @property SortOrder sorting() { // getter
1549       return _sortorder;
1550    }
1551 
1552 
1553 
1554    final void sort() {
1555       if(SortOrder.NONE != _sortorder) {
1556          assert(_sortproc);
1557          ListViewItem[] sitems = items._items;
1558          if(sitems.length > 1) {
1559             sitems = sitems.dup; // So exception won't damage anything.
1560             // Stupid bubble sort. At least it's a "stable sort".
1561             bool swp;
1562             auto sortmax = sitems.length - 1;
1563             size_t iw;
1564             do {
1565                swp = false;
1566                for(iw = 0; iw != sortmax; iw++) {
1567                   //if(sitems[iw] > sitems[iw + 1])
1568                   if(_sortproc(sitems[iw], sitems[iw + 1]) > 0) {
1569                      swp = true;
1570                      ListViewItem lvis = sitems[iw];
1571                      sitems[iw] = sitems[iw + 1];
1572                      sitems[iw + 1] = lvis;
1573                   }
1574                }
1575             } while(swp);
1576 
1577             if(created) {
1578                beginUpdate();
1579                SendMessageA(handle, LVM_DELETEALLITEMS, 0, 0); // Note: this sends LVN_DELETEALLITEMS.
1580                foreach(idx, lvi; sitems) {
1581                   _ins(idx, lvi);
1582                }
1583                endUpdate();
1584             }
1585 
1586             items._items = sitems;
1587          }
1588       }
1589    }
1590 
1591 
1592 
1593    final @property void sorter(int delegate(ListViewItem, ListViewItem) sortproc) { // setter
1594       if(sortproc == this._sortproc) {
1595          return;
1596       }
1597 
1598       if(!sortproc) {
1599          this._sortproc = null;
1600          sorting = SortOrder.NONE;
1601          return;
1602       }
1603 
1604       this._sortproc = sortproc;
1605 
1606       if(SortOrder.NONE == sorting) {
1607          sorting = SortOrder.ASCENDING;
1608       }
1609       sort();
1610    }
1611 
1612    /// ditto
1613    final int delegate(ListViewItem, ListViewItem) sorter() @property { // getter
1614       return _sortproc;
1615    }
1616 
1617 
1618    /+
1619 
1620    // Gets the first visible item.
1621    final @property ListViewItem topItem() { // getter
1622       if(!created) {
1623          return null;
1624       }
1625       // TODO: LVM_GETTOPINDEX
1626    }
1627    +/
1628 
1629 
1630 
1631    final @property void arrangeIcons() {
1632       if(created)
1633          // SendMessageA(hwnd, LVM_ARRANGE, LVA_DEFAULT, 0);
1634       {
1635          prevwproc(LVM_ARRANGE, LVA_DEFAULT, 0);
1636       }
1637    }
1638 
1639    /// ditto
1640    final void arrangeIcons(ListViewAlignment a) {
1641       if(created) {
1642          switch(a) {
1643             case ListViewAlignment.TOP:
1644                //SendMessageA(hwnd, LVM_ARRANGE, LVA_ALIGNTOP, 0);
1645                prevwproc(LVM_ARRANGE, LVA_ALIGNTOP, 0);
1646                break;
1647 
1648             case ListViewAlignment.DEFAULT:
1649                //SendMessageA(hwnd, LVM_ARRANGE, LVA_DEFAULT, 0);
1650                prevwproc(LVM_ARRANGE, LVA_DEFAULT, 0);
1651                break;
1652 
1653             case ListViewAlignment.LEFT:
1654                //SendMessageA(hwnd, LVM_ARRANGE, LVA_ALIGNLEFT, 0);
1655                prevwproc(LVM_ARRANGE, LVA_ALIGNLEFT, 0);
1656                break;
1657 
1658             case ListViewAlignment.SNAP_TO_GRID:
1659                //SendMessageA(hwnd, LVM_ARRANGE, LVA_SNAPTOGRID, 0);
1660                prevwproc(LVM_ARRANGE, LVA_SNAPTOGRID, 0);
1661                break;
1662 
1663             default:
1664                assert(0);
1665          }
1666       }
1667    }
1668 
1669 
1670 
1671    final void beginUpdate() {
1672       SendMessageA(handle, WM_SETREDRAW, false, 0);
1673    }
1674 
1675    /// ditto
1676    final void endUpdate() {
1677       SendMessageA(handle, WM_SETREDRAW, true, 0);
1678       invalidate(true); // Show updates.
1679    }
1680 
1681 
1682 
1683    final void clear() {
1684       litems.clear();
1685    }
1686 
1687 
1688 
1689    final void ensureVisible(int index) {
1690       // Can only be visible if it's created. Check if correct implementation.
1691       createControl();
1692 
1693       //if(created)
1694       // SendMessageA(hwnd, LVM_ENSUREVISIBLE, cast(WPARAM)index, FALSE);
1695       prevwproc(LVM_ENSUREVISIBLE, cast(WPARAM)index, FALSE);
1696    }
1697 
1698 
1699    /+
1700 
1701    // Returns null if no item is at this location.
1702    final ListViewItem getItemAt(int x, int y) {
1703       // LVM_FINDITEM LVFI_NEARESTXY ? since it's nearest, need to see if it's really at that location.
1704       // TODO
1705    }
1706    +/
1707 
1708 
1709 
1710    final Rect getItemRect(int index) {
1711       if(created) {
1712          RECT rect;
1713          rect.left = LVIR_BOUNDS;
1714          if(prevwproc(LVM_GETITEMRECT, cast(WPARAM)index, cast(LPARAM)&rect)) {
1715             return Rect(&rect);
1716          }
1717       }
1718       return Rect(0, 0, 0, 0);
1719    }
1720 
1721    /// ditto
1722    final Rect getItemRect(int index, ItemBoundsPortion ibp) {
1723       if(created) {
1724          RECT rect;
1725          switch(ibp) {
1726             case ItemBoundsPortion.ENTIRE:
1727                rect.left = LVIR_BOUNDS;
1728                break;
1729 
1730             case ItemBoundsPortion.ICON:
1731                rect.left = LVIR_ICON;
1732                break;
1733 
1734             case ItemBoundsPortion.ITEM_ONLY:
1735                rect.left = LVIR_SELECTBOUNDS; // ?
1736                break;
1737 
1738             case ItemBoundsPortion.LABEL:
1739                rect.left = LVIR_LABEL;
1740                break;
1741 
1742             default:
1743                assert(0);
1744          }
1745          if(prevwproc(LVM_GETITEMRECT, cast(WPARAM)index, cast(LPARAM)&rect)) {
1746             return Rect(&rect);
1747          }
1748       }
1749       return Rect(0, 0, 0, 0);
1750    }
1751 
1752 
1753    version(DFL_NO_IMAGELIST) {
1754    }
1755    else {
1756 
1757       final @property void largeImageList(ImageList imglist) { // setter
1758          if(isHandleCreated) {
1759             prevwproc(LVM_SETIMAGELIST, LVSIL_NORMAL,
1760                       cast(LPARAM)(imglist ? imglist.handle : cast(HIMAGELIST)null));
1761          }
1762 
1763          _lgimglist = imglist;
1764       }
1765 
1766       /// ditto
1767       final @property ImageList largeImageList() { // getter
1768          return _lgimglist;
1769       }
1770 
1771 
1772 
1773       final @property void smallImageList(ImageList imglist) { // setter
1774          if(isHandleCreated) {
1775             prevwproc(LVM_SETIMAGELIST, LVSIL_SMALL,
1776                       cast(LPARAM)(imglist ? imglist.handle : cast(HIMAGELIST)null));
1777          }
1778 
1779          _smimglist = imglist;
1780       }
1781 
1782       /// ditto
1783       final @property ImageList smallImageList() { // getter
1784          return _smimglist;
1785       }
1786 
1787 
1788       /+
1789 
1790       final @property void stateImageList(ImageList imglist) { // setter
1791          if(isHandleCreated) {
1792             prevwproc(LVM_SETIMAGELIST, LVSIL_STATE,
1793                       cast(LPARAM)(imglist ? imglist.handle : cast(HIMAGELIST)null));
1794          }
1795 
1796          _stimglist = imglist;
1797       }
1798 
1799       /// ditto
1800       final @property ImageList stateImageList() { // getter
1801          return _stimglist;
1802       }
1803       +/
1804    }
1805 
1806 
1807    // TODO:
1808    //  itemActivate, itemDrag
1809    //CancelEventHandler selectedIndexChanging; // ?
1810 
1811    Event!(ListView, ColumnClickEventArgs) columnClick; ///
1812    Event!(ListView, LabelEditEventArgs) afterLabelEdit; ///
1813    Event!(ListView, LabelEditEventArgs) beforeLabelEdit; ///
1814    //Event!(ListView, ItemCheckEventArgs) itemCheck; ///
1815    Event!(ListView, ItemCheckedEventArgs) itemChecked; ///
1816    Event!(ListView, EventArgs) selectedIndexChanged; ///
1817 
1818 
1819 
1820    protected void onColumnClick(ColumnClickEventArgs ea) {
1821       columnClick(this, ea);
1822    }
1823 
1824 
1825 
1826    protected void onAfterLabelEdit(LabelEditEventArgs ea) {
1827       afterLabelEdit(this, ea);
1828    }
1829 
1830 
1831 
1832    protected void onBeforeLabelEdit(LabelEditEventArgs ea) {
1833       beforeLabelEdit(this, ea);
1834    }
1835 
1836 
1837    /+
1838    protected void onItemCheck(ItemCheckEventArgs ea) {
1839       itemCheck(this, ea);
1840    }
1841    +/
1842 
1843 
1844 
1845    protected void onItemChecked(ItemCheckedEventArgs ea) {
1846       itemChecked(this, ea);
1847    }
1848 
1849 
1850 
1851    protected void onSelectedIndexChanged(EventArgs ea) {
1852       selectedIndexChanged(this, ea);
1853    }
1854 
1855 
1856    protected override @property Size defaultSize() { // getter
1857       return Size(120, 95);
1858    }
1859 
1860 
1861    static @property Color defaultBackColor() { // getter
1862       return SystemColors.window;
1863    }
1864 
1865 
1866    static @property Color defaultForeColor() { // getter
1867       return SystemColors.windowText;
1868    }
1869 
1870 
1871    protected override void createParams(ref CreateParams cp) {
1872       super.createParams(cp);
1873 
1874       cp.className = LISTVIEW_CLASSNAME;
1875    }
1876 
1877 
1878    protected override void prevWndProc(ref Message msg) {
1879       switch(msg.msg) {
1880          case WM_MOUSEHOVER:
1881             if(!hoverSelection) {
1882                return;
1883             }
1884             break;
1885 
1886          default:
1887       }
1888 
1889       //msg.result = CallWindowProcA(listviewPrevWndProc, msg.hWnd, msg.msg, msg.wParam, msg.lParam);
1890       msg.result = dfl.internal.utf.callWindowProc(listviewPrevWndProc, msg.hWnd, msg.msg, msg.wParam, msg.lParam);
1891    }
1892 
1893 
1894    protected override void wndProc(ref Message m) {
1895       // TODO: support the listview messages.
1896 
1897       switch(m.msg) {
1898             /+
1899          case WM_PAINT:
1900             // This seems to be the only way to display columns correctly.
1901             prevWndProc(m);
1902             return;
1903             +/
1904 
1905          case LVM_ARRANGE:
1906             m.result = FALSE;
1907             return;
1908 
1909          case LVM_DELETEALLITEMS:
1910             litems.clear();
1911             m.result = TRUE;
1912             return;
1913 
1914          case LVM_DELETECOLUMN:
1915             cols.removeAt(cast(int)m.wParam);
1916             m.result = TRUE;
1917             return;
1918 
1919          case LVM_DELETEITEM:
1920             litems.removeAt(cast(int)m.wParam);
1921             m.result = TRUE;
1922             return;
1923 
1924          case LVM_INSERTCOLUMNA:
1925          case LVM_INSERTCOLUMNW:
1926             m.result = -1;
1927             return;
1928 
1929          case LVM_INSERTITEMA:
1930          case LVM_INSERTITEMW:
1931             m.result = -1;
1932             return;
1933 
1934          case LVM_SETBKCOLOR:
1935             backColor = Color.fromRgb(cast(COLORREF)m.lParam);
1936             m.result = TRUE;
1937             return;
1938 
1939          case LVM_SETCALLBACKMASK:
1940             m.result = FALSE;
1941             return;
1942 
1943          case LVM_SETCOLUMNA:
1944          case LVM_SETCOLUMNW:
1945             m.result = FALSE;
1946             return;
1947 
1948          case LVM_SETCOLUMNWIDTH:
1949             return;
1950 
1951          case LVM_SETIMAGELIST:
1952             m.result = cast(LRESULT)0;
1953             return;
1954 
1955          case LVM_SETITEMA:
1956             m.result = FALSE;
1957             return;
1958 
1959          case LVM_SETITEMSTATE:
1960             m.result = FALSE;
1961             return;
1962 
1963          case LVM_SETITEMTEXTA:
1964          case LVM_SETITEMTEXTW:
1965             m.result = FALSE;
1966             return;
1967 
1968          //case LVM_SETTEXTBKCOLOR:
1969 
1970          case LVM_SETTEXTCOLOR:
1971             foreColor = Color.fromRgb(cast(COLORREF)m.lParam);
1972             m.result = TRUE;
1973             return;
1974 
1975          case LVM_SORTITEMS:
1976             m.result = FALSE;
1977             return;
1978 
1979          default:
1980       }
1981       super.wndProc(m);
1982    }
1983 
1984 
1985    protected override void onHandleCreated(EventArgs ea) {
1986       super.onHandleCreated(ea);
1987 
1988       //SendMessageA(hwnd, LVM_SETEXTENDEDLISTVIEWSTYLE, wlvexstyle, wlvexstyle);
1989       prevwproc(LVM_SETEXTENDEDLISTVIEWSTYLE, 0, wlvexstyle); // wparam=0 sets all.
1990 
1991       Color color;
1992       COLORREF cref;
1993 
1994       color = backColor;
1995       if(Color.empty == color) {
1996          cref = CLR_NONE;
1997       } else {
1998          cref = color.toRgb();
1999       }
2000       prevwproc(LVM_SETBKCOLOR, 0, cast(LPARAM)cref);
2001       prevwproc(LVM_SETTEXTBKCOLOR, 0, cast(LPARAM)cref);
2002 
2003       //prevwproc(LVM_SETTEXTCOLOR, 0, foreColor.toRgb()); // DMD 0.125: cast(Control )(this).foreColor() is not an lvalue
2004       color = foreColor;
2005       prevwproc(LVM_SETTEXTCOLOR, 0, cast(LPARAM)color.toRgb());
2006 
2007       version(DFL_NO_IMAGELIST) {
2008       }
2009       else {
2010          if(_lgimglist) {
2011             prevwproc(LVM_SETIMAGELIST, LVSIL_NORMAL, cast(LPARAM)_lgimglist.handle);
2012          }
2013          if(_smimglist) {
2014             prevwproc(LVM_SETIMAGELIST, LVSIL_SMALL, cast(LPARAM)_smimglist.handle);
2015          }
2016          //if(_stimglist)
2017          // prevwproc(LVM_SETIMAGELIST, LVSIL_STATE, cast(LPARAM)_stimglist.handle);
2018       }
2019 
2020       cols.doListHeaders();
2021       litems.doListItems();
2022 
2023       recalcEntire(); // Fix frame.
2024    }
2025 
2026 
2027    protected override void onReflectedMessage(ref Message m) {
2028       super.onReflectedMessage(m);
2029 
2030       switch(m.msg) {
2031          case WM_NOTIFY: {
2032                NMHDR* nmh;
2033                nmh = cast(NMHDR*)m.lParam;
2034                switch(nmh.code) {
2035                   case LVN_GETDISPINFOA:
2036                      if(dfl.internal.utf.useUnicode) {
2037                         break;
2038                      } else {
2039                         LV_DISPINFOA* lvdi;
2040                         lvdi = cast(LV_DISPINFOA*)nmh;
2041 
2042                         // Note: might want to verify it's a valid ListViewItem.
2043 
2044                         ListViewItem item;
2045                         item = cast(ListViewItem)cast(void*)lvdi.item.lParam;
2046 
2047                         if(!lvdi.item.iSubItem) { // Item.
2048                            version(DFL_NO_IMAGELIST) {
2049                            }
2050                            else {
2051                               if(lvdi.item.mask & LVIF_IMAGE) {
2052                                  lvdi.item.iImage = item._imgidx;
2053                               }
2054                            }
2055 
2056                            if(lvdi.item.mask & LVIF_TEXT) {
2057                               lvdi.item.pszText = cast(typeof(lvdi.item.pszText))item.calltxt.ansi;
2058                            }
2059                         } else { // Sub item.
2060                            if(lvdi.item.mask & LVIF_TEXT) {
2061                               if(lvdi.item.iSubItem <= item.subItems.length) {
2062                                  lvdi.item.pszText = cast(typeof(lvdi.item.pszText))item.subItems[lvdi.item.iSubItem - 1].calltxt.ansi;
2063                               }
2064                            }
2065                         }
2066                         break;
2067                      }
2068 
2069                   case LVN_GETDISPINFOW: {
2070                         Dstring text;
2071                         LV_DISPINFOW* lvdi;
2072                         lvdi = cast(LV_DISPINFOW*)nmh;
2073 
2074                         // Note: might want to verify it's a valid ListViewItem.
2075 
2076                         ListViewItem item;
2077                         item = cast(ListViewItem)cast(void*)lvdi.item.lParam;
2078 
2079                         if(!lvdi.item.iSubItem) { // Item.
2080                            version(DFL_NO_IMAGELIST) {
2081                            }
2082                            else {
2083                               if(lvdi.item.mask & LVIF_IMAGE) {
2084                                  lvdi.item.iImage = item._imgidx;
2085                               }
2086                            }
2087 
2088                            if(lvdi.item.mask & LVIF_TEXT) {
2089                               lvdi.item.pszText = cast(typeof(lvdi.item.pszText))item.calltxt.unicode;
2090                            }
2091                         } else { // Sub item.
2092                            if(lvdi.item.mask & LVIF_TEXT) {
2093                               if(lvdi.item.iSubItem <= item.subItems.length) {
2094                                  lvdi.item.pszText = cast(typeof(lvdi.item.pszText))item.subItems[lvdi.item.iSubItem - 1].calltxt.unicode;
2095                               }
2096                            }
2097                         }
2098                      }
2099                      break;
2100 
2101                      /+
2102                   case LVN_ITEMCHANGING: {
2103                         auto nmlv = cast(NM_LISTVIEW*)nmh;
2104                         if(-1 != nmlv.iItem) {
2105                            UINT stchg = nmlv.uNewState ^ nmlv.uOldState;
2106                            if(stchg & (3 << 12)) {
2107                               // Note: not tested.
2108                               scope ItemCheckEventArgs ea = new ItemCheckEventArgs(nmlv.iItem,
2109                                     (((nmlv.uNewState >> 12) & 3) - 1) ? CheckState.CHECKED : CheckState.UNCHECKED,
2110                                     (((nmlv.uOldState >> 12) & 3) - 1) ? CheckState.CHECKED : CheckState.UNCHECKED);
2111                               onItemCheck(ea);
2112                            }
2113                         }
2114                      }
2115                      break;
2116                      +/
2117 
2118                   case LVN_ITEMCHANGED: {
2119                         auto nmlv = cast(NM_LISTVIEW*)nmh;
2120                         if(-1 != nmlv.iItem) {
2121                            if(nmlv.uChanged & LVIF_STATE) {
2122                               UINT stchg = nmlv.uNewState ^ nmlv.uOldState;
2123 
2124                               //if(stchg & LVIS_SELECTED)
2125                               {
2126                                  // Only fire for the selected one; don't fire twice for old/new.
2127                                  if(nmlv.uNewState & LVIS_SELECTED) {
2128                                     onSelectedIndexChanged(EventArgs.empty);
2129                                  }
2130                               }
2131 
2132                               if(stchg & (3 << 12)) {
2133                                  scope ItemCheckedEventArgs ea = new ItemCheckedEventArgs(items[nmlv.iItem]);
2134                                  onItemChecked(ea);
2135                               }
2136                            }
2137                         }
2138                      }
2139                      break;
2140 
2141                   case LVN_COLUMNCLICK: {
2142                         auto nmlv = cast(NM_LISTVIEW*)nmh;
2143                         scope ccea = new ColumnClickEventArgs(nmlv.iSubItem);
2144                         onColumnClick(ccea);
2145                      }
2146                      break;
2147 
2148                   case LVN_BEGINLABELEDITW:
2149                      goto begin_label_edit;
2150 
2151                   case LVN_BEGINLABELEDITA:
2152                      if(dfl.internal.utf.useUnicode) {
2153                         break;
2154                      }
2155                   begin_label_edit:
2156 
2157                      {
2158                         LV_DISPINFOA* nmdi;
2159                         nmdi = cast(LV_DISPINFOA*)nmh;
2160                         if(nmdi.item.iSubItem) {
2161                            m.result = TRUE;
2162                            break;
2163                         }
2164                         ListViewItem lvitem;
2165                         lvitem = cast(ListViewItem)cast(void*)nmdi.item.lParam;
2166                         scope LabelEditEventArgs leea = new LabelEditEventArgs(lvitem);
2167                         onBeforeLabelEdit(leea);
2168                         m.result = leea.cancelEdit;
2169                      }
2170                      break;
2171 
2172                   case LVN_ENDLABELEDITW: {
2173                         Dstring label;
2174                         LV_DISPINFOW* nmdi;
2175                         nmdi = cast(LV_DISPINFOW*)nmh;
2176                         if(nmdi.item.pszText) {
2177                            ListViewItem lvitem;
2178                            lvitem = cast(ListViewItem)cast(void*)nmdi.item.lParam;
2179                            if(nmdi.item.iSubItem) {
2180                               m.result = FALSE;
2181                               break;
2182                            }
2183                            label = fromUnicodez(nmdi.item.pszText);
2184                            scope LabelEditEventArgs nleea = new LabelEditEventArgs(lvitem, label);
2185                            onAfterLabelEdit(nleea);
2186                            if(nleea.cancelEdit) {
2187                               m.result = FALSE;
2188                            } else {
2189                               // TODO: check if correct implementation.
2190                               // Update the lvitem's cached text..
2191                               lvitem.settextin(label);
2192 
2193                               m.result = TRUE;
2194                            }
2195                         }
2196                      }
2197                      break;
2198 
2199                   case LVN_ENDLABELEDITA:
2200                      if(dfl.internal.utf.useUnicode) {
2201                         break;
2202                      } else {
2203                         Dstring label;
2204                         LV_DISPINFOA* nmdi;
2205                         nmdi = cast(LV_DISPINFOA*)nmh;
2206                         if(nmdi.item.pszText) {
2207                            ListViewItem lvitem;
2208                            lvitem = cast(ListViewItem)cast(void*)nmdi.item.lParam;
2209                            if(nmdi.item.iSubItem) {
2210                               m.result = FALSE;
2211                               break;
2212                            }
2213                            label = fromAnsiz(nmdi.item.pszText);
2214                            scope LabelEditEventArgs nleea = new LabelEditEventArgs(lvitem, label);
2215                            onAfterLabelEdit(nleea);
2216                            if(nleea.cancelEdit) {
2217                               m.result = FALSE;
2218                            } else {
2219                               // TODO: check if correct implementation.
2220                               // Update the lvitem's cached text..
2221                               lvitem.settextin(label);
2222 
2223                               m.result = TRUE;
2224                            }
2225                         }
2226                         break;
2227                      }
2228 
2229                   default:
2230                }
2231             }
2232             break;
2233 
2234          default:
2235       }
2236    }
2237 
2238 
2239  private:
2240    DWORD wlvexstyle = 0;
2241    ListViewItemCollection litems;
2242    ColumnHeaderCollection cols;
2243    SelectedIndexCollection selidxcollection;
2244    SelectedItemCollection selobjcollection;
2245    SortOrder _sortorder = SortOrder.NONE;
2246    CheckedIndexCollection checkedis;
2247    int delegate(ListViewItem, ListViewItem) _sortproc;
2248    version(DFL_NO_IMAGELIST) {
2249    }
2250    else {
2251       ImageList _lgimglist, _smimglist;
2252       //ImageList _stimglist;
2253    }
2254 
2255 
2256    int _defsortproc(ListViewItem a, ListViewItem b) {
2257       return a.opCmp(b);
2258    }
2259 
2260 
2261    DWORD _lvexstyle() {
2262       //if(created)
2263       // wlvexstyle = cast(DWORD)SendMessageA(hwnd, LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0);
2264       // wlvexstyle = cast(DWORD)prevwproc(LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0);
2265       return wlvexstyle;
2266    }
2267 
2268 
2269    void _lvexstyle(DWORD flags) {
2270       DWORD _b4;
2271       _b4 = wlvexstyle;
2272 
2273       wlvexstyle = flags;
2274       if(created) {
2275          // hwnd, msg, mask, flags
2276          //SendMessageA(hwnd, LVM_SETEXTENDEDLISTVIEWSTYLE, flags ^ _b4, wlvexstyle);
2277          prevwproc(LVM_SETEXTENDEDLISTVIEWSTYLE, flags ^ _b4, wlvexstyle);
2278          //redrawEntire(); // Need to recalc the frame ?
2279       }
2280    }
2281 
2282 
2283    void _lvexstyle(DWORD mask, DWORD flags)
2284    in {
2285       assert(mask);
2286    }
2287    body {
2288       wlvexstyle = (wlvexstyle & ~mask) | (flags & mask);
2289       if(created) {
2290          // hwnd, msg, mask, flags
2291          //SendMessageA(hwnd, LVM_SETEXTENDEDLISTVIEWSTYLE, mask, flags);
2292          prevwproc(LVM_SETEXTENDEDLISTVIEWSTYLE, mask, flags);
2293          //redrawEntire(); // Need to recalc the frame ?
2294       }
2295    }
2296 
2297 
2298    // If -subItemIndex- is 0 it's an item not a sub item.
2299    // Returns the insertion index or -1 on failure.
2300    package final LRESULT _ins(int index, LPARAM lparam, Dstring itemText, int subItemIndex, int imageIndex = -1)
2301    in {
2302       assert(created);
2303    }
2304    body {
2305       /+
2306       cprintf("^ Insert item:  index=%d, lparam=0x%X, text='%.*s', subItemIndex=%d\n",
2307       index, lparam, itemText.length > 20 ? 20 : itemText.length, cast(char*)itemText, subItemIndex);
2308       +/
2309 
2310       LV_ITEMA lvi;
2311       lvi.mask = LVIF_TEXT | LVIF_PARAM;
2312       version(DFL_NO_IMAGELIST) {
2313       }
2314       else
2315       {
2316          //if(-1 != imageIndex)
2317          if(!subItemIndex) {
2318             lvi.mask |= LVIF_IMAGE;
2319          }
2320          //lvi.iImage = imageIndex;
2321          lvi.iImage = I_IMAGECALLBACK;
2322       }
2323       lvi.iItem = index;
2324       lvi.iSubItem = subItemIndex;
2325       //lvi.pszText = toStringz(itemText);
2326       lvi.pszText = LPSTR_TEXTCALLBACKA;
2327       lvi.lParam = lparam;
2328       return prevwproc(LVM_INSERTITEMA, 0, cast(LPARAM)&lvi);
2329    }
2330 
2331 
2332    package final LRESULT _ins(int index, ListViewItem item) {
2333       //return _ins(index, cast(LPARAM)cast(void*)item, item.text, 0);
2334       version(DFL_NO_IMAGELIST) {
2335          return _ins(index, cast(LPARAM)cast(void*)item, item.text, 0, -1);
2336       }
2337       else {
2338          return _ins(index, cast(LPARAM)cast(void*)item, item.text, 0, item._imgidx);
2339       }
2340    }
2341 
2342 
2343    package final LRESULT _ins(int index, ListViewSubItem subItem, int subItemIndex)
2344    in {
2345       assert(subItemIndex > 0);
2346    }
2347    body {
2348       return _ins(index, cast(LPARAM)cast(void*)subItem, subItem.text, subItemIndex);
2349    }
2350 
2351 
2352    package final LRESULT _ins(int index, ColumnHeader header) {
2353       // TODO: column inserted at index 0 can only be left aligned, so will need to
2354       // insert a dummy column to change the alignment, then delete the dummy column.
2355 
2356       //LV_COLUMNA lvc;
2357       LvColumn lvc;
2358       lvc.mask = LVCF_FMT | LVCF_SUBITEM | LVCF_TEXT | LVCF_WIDTH;
2359       switch(header.textAlign) {
2360          case HorizontalAlignment.RIGHT:
2361             lvc.fmt = LVCFMT_RIGHT;
2362             break;
2363 
2364          case HorizontalAlignment.CENTER:
2365             lvc.fmt = LVCFMT_CENTER;
2366             break;
2367 
2368          default:
2369             lvc.fmt = LVCFMT_LEFT;
2370       }
2371       lvc.cx = header.width;
2372       lvc.iSubItem = index; // iSubItem is probably only used when retrieving column info.
2373       if(dfl.internal.utf.useUnicode) {
2374          lvc.lvcw.pszText = cast(typeof(lvc.lvcw.pszText))dfl.internal.utf.toUnicodez(header.text);
2375          return prevwproc(LVM_INSERTCOLUMNW, cast(WPARAM)index, cast(LPARAM)&lvc.lvcw);
2376       } else {
2377          lvc.lvca.pszText = cast(typeof(lvc.lvca.pszText))dfl.internal.utf.toAnsiz(header.text);
2378          return prevwproc(LVM_INSERTCOLUMNA, cast(WPARAM)index, cast(LPARAM)&lvc.lvca);
2379       }
2380    }
2381 
2382 
2383    // If -subItemIndex- is 0 it's an item not a sub item.
2384    // Returns FALSE on failure.
2385    LRESULT updateItem(int index)
2386    in {
2387       assert(created);
2388    }
2389    body {
2390       return prevwproc(LVM_REDRAWITEMS, cast(WPARAM)index, cast(LPARAM)index);
2391    }
2392 
2393    LRESULT updateItem(ListViewItem item) {
2394       int index;
2395       index = item.index;
2396       assert(-1 != index);
2397       return updateItem(index);
2398    }
2399 
2400 
2401    LRESULT updateItemText(int index, Dstring newText, int subItemIndex = 0) {
2402       return updateItem(index);
2403    }
2404 
2405    LRESULT updateItemText(ListViewItem item, Dstring newText, int subItemIndex = 0) {
2406       return updateItem(item);
2407    }
2408 
2409 
2410    LRESULT updateColumnText(int colIndex, Dstring newText) {
2411       //LV_COLUMNA lvc;
2412       LvColumn lvc;
2413 
2414       lvc.mask = LVCF_TEXT;
2415       if(dfl.internal.utf.useUnicode) {
2416          lvc.lvcw.pszText = cast(typeof(lvc.lvcw.pszText))dfl.internal.utf.toUnicodez(newText);
2417          return prevwproc(LVM_SETCOLUMNW, cast(WPARAM)colIndex, cast(LPARAM)&lvc.lvcw);
2418       } else {
2419          lvc.lvca.pszText = cast(typeof(lvc.lvca.pszText))dfl.internal.utf.toAnsiz(newText);
2420          return prevwproc(LVM_SETCOLUMNA, cast(WPARAM)colIndex, cast(LPARAM)&lvc.lvca);
2421       }
2422    }
2423 
2424 
2425    LRESULT updateColumnText(ColumnHeader col, Dstring newText) {
2426       int colIndex;
2427       colIndex = columns.indexOf(col);
2428       assert(-1 != colIndex);
2429       return updateColumnText(colIndex, newText);
2430    }
2431 
2432 
2433    LRESULT updateColumnAlign(int colIndex, HorizontalAlignment halign) {
2434       LV_COLUMNA lvc;
2435       lvc.mask = LVCF_FMT;
2436       switch(halign) {
2437          case HorizontalAlignment.RIGHT:
2438             lvc.fmt = LVCFMT_RIGHT;
2439             break;
2440 
2441          case HorizontalAlignment.CENTER:
2442             lvc.fmt = LVCFMT_CENTER;
2443             break;
2444 
2445          default:
2446             lvc.fmt = LVCFMT_LEFT;
2447       }
2448       return prevwproc(LVM_SETCOLUMNA, cast(WPARAM)colIndex, cast(LPARAM)&lvc);
2449    }
2450 
2451 
2452    LRESULT updateColumnAlign(ColumnHeader col, HorizontalAlignment halign) {
2453       int colIndex;
2454       colIndex = columns.indexOf(col);
2455       assert(-1 != colIndex);
2456       return updateColumnAlign(colIndex, halign);
2457    }
2458 
2459 
2460    LRESULT updateColumnWidth(int colIndex, int w) {
2461       LV_COLUMNA lvc;
2462       lvc.mask = LVCF_WIDTH;
2463       lvc.cx = w;
2464       return prevwproc(LVM_SETCOLUMNA, cast(WPARAM)colIndex, cast(LPARAM)&lvc);
2465    }
2466 
2467 
2468    LRESULT updateColumnWidth(ColumnHeader col, int w) {
2469       int colIndex;
2470       colIndex = columns.indexOf(col);
2471       assert(-1 != colIndex);
2472       return updateColumnWidth(colIndex, w);
2473    }
2474 
2475 
2476    int getColumnWidth(int colIndex) {
2477       LV_COLUMNA lvc;
2478       lvc.mask = LVCF_WIDTH;
2479       lvc.cx = -1;
2480       prevwproc(LVM_GETCOLUMNA, cast(WPARAM)colIndex, cast(LPARAM)&lvc);
2481       return lvc.cx;
2482    }
2483 
2484 
2485    int getColumnWidth(ColumnHeader col) {
2486       int colIndex;
2487       colIndex = columns.indexOf(col);
2488       assert(-1 != colIndex);
2489       return getColumnWidth(colIndex);
2490    }
2491 
2492 
2493  package:
2494  final:
2495    LRESULT prevwproc(UINT msg, WPARAM wparam, LPARAM lparam) {
2496       //return CallWindowProcA(listviewPrevWndProc, hwnd, msg, wparam, lparam);
2497       return dfl.internal.utf.callWindowProc(listviewPrevWndProc, hwnd, msg, wparam, lparam);
2498    }
2499 }
2500