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