1 // Written by Christopher E. Miller
2 // See the included license.txt for copyright and license details.
3 
4 module dfl.listbox;
5 
6 import std.algorithm;
7 import core.sys.windows.windows;
8 
9 import dfl.exception;
10 import dfl.application;
11 import dfl.base;
12 import dfl.collections;
13 import dfl.control;
14 import dfl.drawing;
15 import dfl.event;
16 import dfl.internal.dlib;
17 static import dfl.internal.utf;
18 
19 
20 private extern (C) void* memmove(void*, void*, size_t len);
21 
22 private extern (Windows) void _initListbox();
23 
24 alias ListString = StringObject;
25 
26 abstract class ListControl : ControlSuperClass {
27 
28    final Dstring getItemText(Object item) {
29       return getObjectString(item);
30    }
31 
32    //EventHandler selectedValueChanged;
33    Event!(ListControl, EventArgs) selectedValueChanged;
34 
35    abstract @property void selectedIndex(int idx);
36 
37    abstract @property int selectedIndex();
38 
39    abstract @property void selectedValue(Object val);
40 
41    abstract @property void selectedValue(Dstring str);
42 
43    abstract @property Object selectedValue();
44 
45    static @property Color defaultBackColor() {
46       return SystemColors.window;
47    }
48 
49    override @property Color backColor() {
50       if (Color.empty == backc) {
51          return defaultBackColor;
52       }
53       return backc;
54    }
55 
56    alias backColor = Control.backColor; // Overload.
57 
58    static @property Color defaultForeColor() {
59       return SystemColors.windowText;
60    }
61 
62    override @property Color foreColor() {
63       if (Color.empty == forec) {
64          return defaultForeColor;
65       }
66       return forec;
67    }
68 
69    alias foreColor = Control.foreColor; // Overload.
70 
71    this() {
72    }
73 
74 protected:
75 
76    void onSelectedValueChanged(EventArgs ea) {
77       selectedValueChanged(this, ea);
78    }
79 
80    // Index change causes the value to be changed.
81    void onSelectedIndexChanged(EventArgs ea) {
82       onSelectedValueChanged(ea); // This appears to be correct.
83    }
84 }
85 
86 enum SelectionMode : ubyte {
87    ONE,
88    NONE,
89    MULTI_SIMPLE,
90    MULTI_EXTENDED,
91 }
92 
93 class ListBox : ListControl {
94 
95    static class SelectedIndexCollection {
96       deprecated alias count = length;
97 
98       @property int length() {
99          if (!lbox.isHandleCreated) {
100             return 0;
101          }
102 
103          if (lbox.isMultSel()) {
104             return lbox.prevwproc(LB_GETSELCOUNT, 0, 0);
105          } else {
106             return (lbox.selectedIndex == -1) ? 0 : 1;
107          }
108       }
109 
110       int opIndex(int idx) {
111          foreach (int onidx; this) {
112             if (!idx) {
113                return onidx;
114             }
115             idx--;
116          }
117 
118          // If it's not found it's out of bounds and bad things happen.
119          assert(0);
120       }
121 
122       bool contains(int idx) {
123          return indexOf(idx) != -1;
124       }
125 
126       int indexOf(int idx) {
127          int i = 0;
128          foreach (int onidx; this) {
129             if (onidx == idx) {
130                return i;
131             }
132             i++;
133          }
134          return -1;
135       }
136 
137       int opApply(int delegate(ref int) dg) {
138          int result = 0;
139 
140          if (lbox.isMultSel()) {
141             int[] items;
142             items = new int[length];
143             if (items.length != lbox.prevwproc(LB_GETSELITEMS, items.length,
144                   cast(LPARAM) cast(int*) items)) {
145                throw new DflException("Unable to enumerate selected list items");
146             }
147             foreach (int _idx; items) {
148                int idx = _idx; // Prevent ref.
149                result = dg(idx);
150                if (result) {
151                   break;
152                }
153             }
154          } else {
155             int idx;
156             idx = lbox.selectedIndex;
157             if (-1 != idx) {
158                result = dg(idx);
159             }
160          }
161          return result;
162       }
163 
164       mixin OpApplyAddIndex!(opApply, int);
165 
166       protected this(ListBox lb) {
167          lbox = lb;
168       }
169 
170    package:
171       ListBox lbox;
172    }
173 
174    static class SelectedObjectCollection {
175       deprecated alias count = length;
176 
177       @property int length() {
178          if (!lbox.isHandleCreated) {
179             return 0;
180          }
181 
182          if (lbox.isMultSel()) {
183             return lbox.prevwproc(LB_GETSELCOUNT, 0, 0);
184          } else {
185             return (lbox.selectedIndex == -1) ? 0 : 1;
186          }
187       }
188 
189       Object opIndex(int idx) {
190          foreach (Object obj; this) {
191             if (!idx) {
192                return obj;
193             }
194             idx--;
195          }
196 
197          // If it's not found it's out of bounds and bad things happen.
198          assert(0);
199       }
200 
201       bool contains(Object obj) {
202          return indexOf(obj) != -1;
203       }
204 
205       bool contains(Dstring str) {
206          return indexOf(str) != -1;
207       }
208 
209       int indexOf(Object obj) {
210          int idx = 0;
211          foreach (Object onobj; this) {
212             if (onobj == obj) { // Not using is.
213                return idx;
214             }
215             idx++;
216          }
217          return -1;
218       }
219 
220       int indexOf(Dstring str) {
221          int idx = 0;
222          foreach (Object onobj; this) {
223             //if(getObjectString(onobj) is str && getObjectString(onobj).length == str.length)
224             if (getObjectString(onobj) == str) {
225                return idx;
226             }
227             idx++;
228          }
229          return -1;
230       }
231 
232       // Used internally.
233       int _opApply(int delegate(ref Object) dg) { // package
234          int result = 0;
235 
236          if (lbox.isMultSel()) {
237             int[] items;
238             items = new int[length];
239             if (items.length != lbox.prevwproc(LB_GETSELITEMS, items.length,
240                   cast(LPARAM) cast(int*) items)) {
241                throw new DflException("Unable to enumerate selected list items");
242             }
243             foreach (int idx; items) {
244                Object obj;
245                obj = lbox.items[idx];
246                result = dg(obj);
247                if (result) {
248                   break;
249                }
250             }
251          } else {
252             Object obj;
253             obj = lbox.selectedItem;
254             if (obj) {
255                result = dg(obj);
256             }
257          }
258          return result;
259       }
260 
261       // Used internally.
262       int _opApply(int delegate(ref Dstring) dg) { // package
263          int result = 0;
264 
265          if (lbox.isMultSel()) {
266             int[] items;
267             items = new int[length];
268             if (items.length != lbox.prevwproc(LB_GETSELITEMS, items.length,
269                   cast(LPARAM) cast(int*) items)) {
270                throw new DflException("Unable to enumerate selected list items");
271             }
272             foreach (int idx; items) {
273                Dstring str;
274                str = getObjectString(lbox.items[idx]);
275                result = dg(str);
276                if (result) {
277                   break;
278                }
279             }
280          } else {
281             Object obj;
282             Dstring str;
283             obj = lbox.selectedItem;
284             if (obj) {
285                str = getObjectString(obj);
286                result = dg(str);
287             }
288          }
289          return result;
290       }
291 
292       mixin OpApplyAddIndex!(_opApply, Dstring);
293 
294       mixin OpApplyAddIndex!(_opApply, Object);
295 
296       // Had to do it this way because: DMD 1.028: -H is broken for mixin identifiers
297       // Note that this way probably prevents opApply from being overridden.
298       alias opApply = _opApply;
299 
300       protected this(ListBox lb) {
301          lbox = lb;
302       }
303 
304    package:
305       ListBox lbox;
306    }
307 
308    enum int DEFAULT_ITEM_HEIGHT = 13;
309 
310    enum int NO_MATCHES = LB_ERR;
311 
312    protected override @property Size defaultSize() {
313       return Size(120, 95);
314    }
315 
316    @property void borderStyle(BorderStyle bs) {
317       final switch (bs) {
318       case BorderStyle.FIXED_3D:
319          _style(_style() & ~WS_BORDER);
320          _exStyle(_exStyle() | WS_EX_CLIENTEDGE);
321          break;
322 
323       case BorderStyle.FIXED_SINGLE:
324          _exStyle(_exStyle() & ~WS_EX_CLIENTEDGE);
325          _style(_style() | WS_BORDER);
326          break;
327 
328       case BorderStyle.NONE:
329          _style(_style() & ~WS_BORDER);
330          _exStyle(_exStyle() & ~WS_EX_CLIENTEDGE);
331          break;
332       }
333 
334       if (isHandleCreated) {
335          redrawEntire();
336       }
337    }
338 
339    @property BorderStyle borderStyle() {
340       if (_exStyle() & WS_EX_CLIENTEDGE) {
341          return BorderStyle.FIXED_3D;
342       } else if (_style() & WS_BORDER) {
343          return BorderStyle.FIXED_SINGLE;
344       }
345       return BorderStyle.NONE;
346    }
347 
348    @property void drawMode(DrawMode dm) {
349       LONG wl = _style() & ~(LBS_OWNERDRAWVARIABLE | LBS_OWNERDRAWFIXED);
350 
351       final switch (dm) {
352       case DrawMode.OWNER_DRAW_VARIABLE:
353          wl |= LBS_OWNERDRAWVARIABLE;
354          break;
355 
356       case DrawMode.OWNER_DRAW_FIXED:
357          wl |= LBS_OWNERDRAWFIXED;
358          break;
359 
360       case DrawMode.NORMAL:
361          break;
362       }
363 
364       _style(wl);
365 
366       _crecreate();
367    }
368 
369    @property DrawMode drawMode() {
370       LONG wl = _style();
371 
372       if (wl & LBS_OWNERDRAWVARIABLE) {
373          return DrawMode.OWNER_DRAW_VARIABLE;
374       }
375       if (wl & LBS_OWNERDRAWFIXED) {
376          return DrawMode.OWNER_DRAW_FIXED;
377       }
378       return DrawMode.NORMAL;
379    }
380 
381    final @property void horizontalExtent(int he) {
382       if (isHandleCreated) {
383          prevwproc(LB_SETHORIZONTALEXTENT, he, 0);
384       }
385 
386       hextent = he;
387    }
388 
389    final @property int horizontalExtent() {
390       if (isHandleCreated) {
391          hextent = cast(int) prevwproc(LB_GETHORIZONTALEXTENT, 0, 0);
392       }
393       return hextent;
394    }
395 
396    final @property void horizontalScrollbar(bool byes) {
397       if (byes) {
398          _style(_style() | WS_HSCROLL);
399       } else {
400          _style(_style() & ~WS_HSCROLL);
401       }
402 
403       _crecreate();
404    }
405 
406    final @property bool horizontalScrollbar() {
407       return (_style() & WS_HSCROLL) != 0;
408    }
409 
410    final @property void integralHeight(bool byes) {
411       if (byes) {
412          _style(_style() & ~LBS_NOINTEGRALHEIGHT);
413       } else {
414          _style(_style() | LBS_NOINTEGRALHEIGHT);
415       }
416 
417       _crecreate();
418    }
419 
420    final @property bool integralHeight() {
421       return (_style() & LBS_NOINTEGRALHEIGHT) == 0;
422    }
423 
424    // This function has no effect if the drawMode is OWNER_DRAW_VARIABLE.
425    final @property void itemHeight(int h) {
426       if (drawMode == DrawMode.OWNER_DRAW_VARIABLE) {
427          return;
428       }
429 
430       iheight = h;
431 
432       if (isHandleCreated) {
433          prevwproc(LB_SETITEMHEIGHT, 0, MAKELPARAM(h, 0));
434       }
435    }
436 
437    // Return value is meaningless when drawMode is OWNER_DRAW_VARIABLE.
438    final @property int itemHeight() {
439       // Requesting it like this when owner draw variable doesn't work.
440       /+
441       if(!isHandleCreated) {
442          return iheight;
443       }
444 
445       int result = prevwproc(LB_GETITEMHEIGHT, 0, 0);
446       if(result == LB_ERR) {
447          result = iheight;   // ?
448       } else {
449          iheight = result;
450       }
451 
452       return result;
453       +/
454 
455       return iheight;
456    }
457 
458    final @property ObjectCollection items() {
459       return icollection;
460    }
461 
462    final @property void multiColumn(bool byes) {
463       // TODO: is this the correct implementation?
464 
465       if (byes) {
466          _style(_style() | LBS_MULTICOLUMN | WS_HSCROLL);
467       } else {
468          _style(_style() & ~(LBS_MULTICOLUMN | WS_HSCROLL));
469       }
470 
471       _crecreate();
472    }
473 
474    final @property bool multiColumn() {
475       return (_style() & LBS_MULTICOLUMN) != 0;
476    }
477 
478    final @property void scrollAlwaysVisible(bool byes) {
479       if (byes) {
480          _style(_style() | LBS_DISABLENOSCROLL);
481       } else {
482          _style(_style() & ~LBS_DISABLENOSCROLL);
483       }
484 
485       _crecreate();
486    }
487 
488    final @property bool scrollAlwaysVisible() {
489       return (_style() & LBS_DISABLENOSCROLL) != 0;
490    }
491 
492    override @property void selectedIndex(int idx) {
493       if (isHandleCreated) {
494          if (isMultSel()) {
495             if (idx == -1) {
496                // Remove all selection.
497 
498                // Not working right.
499                //prevwproc(LB_SELITEMRANGE, false, MAKELPARAM(0, ushort.max));
500 
501                // Get the indices directly because deselecting them during
502                // selidxcollection.foreach could screw it up.
503 
504                int[] items;
505 
506                items = new int[selidxcollection.length];
507                if (items.length != prevwproc(LB_GETSELITEMS, items.length,
508                      cast(LPARAM) cast(int*) items)) {
509                   throw new DflException("Unable to clear selected list items");
510                }
511 
512                foreach (int _idx; items) {
513                   prevwproc(LB_SETSEL, false, _idx);
514                }
515             } else {
516                // ?
517                prevwproc(LB_SETSEL, true, idx);
518             }
519          } else {
520             prevwproc(LB_SETCURSEL, idx, 0);
521          }
522       }
523    }
524 
525    override @property int selectedIndex() {
526       if (isHandleCreated) {
527          if (isMultSel()) {
528             if (selidxcollection.length) {
529                return selidxcollection[0];
530             }
531          } else {
532             LRESULT result;
533             result = prevwproc(LB_GETCURSEL, 0, 0);
534             if (LB_ERR != result) { // Redundant.
535                return cast(int) result;
536             }
537          }
538       }
539       return -1;
540    }
541 
542    final @property void selectedItem(Object o) {
543       int i;
544       i = items.indexOf(o);
545       if (i != -1) {
546          selectedIndex = i;
547       }
548    }
549 
550    final @property void selectedItem(Dstring str) {
551       int i;
552       i = items.indexOf(str);
553       if (i != -1) {
554          selectedIndex = i;
555       }
556    }
557 
558    final @property Object selectedItem() {
559       int idx;
560       idx = selectedIndex;
561       if (idx == -1) {
562          return null;
563       }
564       return items[idx];
565    }
566 
567    override @property void selectedValue(Object val) {
568       selectedItem = val;
569    }
570 
571    override @property void selectedValue(Dstring str) {
572       selectedItem = str;
573    }
574 
575    override @property Object selectedValue() {
576       return selectedItem;
577    }
578 
579    final @property SelectedIndexCollection selectedIndices() {
580       return selidxcollection;
581    }
582 
583    final @property SelectedObjectCollection selectedItems() {
584       return selobjcollection;
585    }
586 
587    @property void selectionMode(SelectionMode selmode) {
588       LONG wl = _style() & ~(LBS_NOSEL | LBS_EXTENDEDSEL | LBS_MULTIPLESEL);
589 
590       final switch (selmode) {
591       case SelectionMode.ONE:
592          break;
593 
594       case SelectionMode.MULTI_SIMPLE:
595          wl |= LBS_MULTIPLESEL;
596          break;
597 
598       case SelectionMode.MULTI_EXTENDED:
599          wl |= LBS_EXTENDEDSEL;
600          break;
601 
602       case SelectionMode.NONE:
603          wl |= LBS_NOSEL;
604          break;
605       }
606 
607       _style(wl);
608 
609       _crecreate();
610    }
611 
612    @property SelectionMode selectionMode() {
613       LONG wl = _style();
614 
615       if (wl & LBS_NOSEL) {
616          return SelectionMode.NONE;
617       }
618       if (wl & LBS_EXTENDEDSEL) {
619          return SelectionMode.MULTI_EXTENDED;
620       }
621       if (wl & LBS_MULTIPLESEL) {
622          return SelectionMode.MULTI_SIMPLE;
623       }
624       return SelectionMode.ONE;
625    }
626 
627    final @property void sorted(bool byes) {
628       /+
629       if(byes) {
630          _style(_style() | LBS_SORT);
631       } else {
632          _style(_style() & ~LBS_SORT);
633       }
634       +/
635       _sorting = byes;
636    }
637 
638    final @property bool sorted() {
639       //return (_style() & LBS_SORT) != 0;
640       return _sorting;
641    }
642 
643    final @property void topIndex(int idx) {
644       if (isHandleCreated) {
645          prevwproc(LB_SETTOPINDEX, idx, 0);
646       }
647    }
648 
649    final @property int topIndex() {
650       if (isHandleCreated) {
651          return prevwproc(LB_GETTOPINDEX, 0, 0);
652       }
653       return 0;
654    }
655 
656    final @property void useTabStops(bool byes) {
657       if (byes) {
658          _style(_style() | LBS_USETABSTOPS);
659       } else {
660          _style(_style() & ~LBS_USETABSTOPS);
661       }
662 
663       _crecreate();
664    }
665 
666    final @property bool useTabStops() {
667       return (_style() & LBS_USETABSTOPS) != 0;
668    }
669 
670    final void beginUpdate() {
671       prevwproc(WM_SETREDRAW, false, 0);
672    }
673 
674    final void endUpdate() {
675       prevwproc(WM_SETREDRAW, true, 0);
676       invalidate(true); // Show updates.
677    }
678 
679    package final bool isMultSel() {
680       return (_style() & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL)) != 0;
681    }
682 
683    final void clearSelected() {
684       if (created) {
685          selectedIndex = -1;
686       }
687    }
688 
689    final int findString(Dstring str, int startIndex) {
690       // TODO: find string if control not created ?
691 
692       int result = NO_MATCHES;
693 
694       if (created) {
695          if (dfl.internal.utf.useUnicode) {
696             result = prevwproc(LB_FINDSTRING, startIndex,
697                cast(LPARAM) dfl.internal.utf.toUnicodez(str));
698          } else {
699             result = prevwproc(LB_FINDSTRING, startIndex,
700                cast(LPARAM) dfl.internal.utf.unsafeAnsiz(str));
701          }
702          if (result == LB_ERR) { // Redundant.
703             result = NO_MATCHES;
704          }
705       }
706 
707       return result;
708    }
709 
710    final int findString(Dstring str) {
711       return findString(str, -1); // Start at beginning.
712    }
713 
714    final int findStringExact(Dstring str, int startIndex) {
715       // TODO: find string if control not created ?
716 
717       int result = NO_MATCHES;
718 
719       if (created) {
720          if (dfl.internal.utf.useUnicode) {
721             result = prevwproc(LB_FINDSTRINGEXACT, startIndex,
722                cast(LPARAM) dfl.internal.utf.toUnicodez(str));
723          } else {
724             result = prevwproc(LB_FINDSTRINGEXACT, startIndex,
725                cast(LPARAM) dfl.internal.utf.unsafeAnsiz(str));
726          }
727          if (result == LB_ERR) { // Redundant.
728             result = NO_MATCHES;
729          }
730       }
731 
732       return result;
733    }
734 
735    final int findStringExact(Dstring str) {
736       return findStringExact(str, -1); // Start at beginning.
737    }
738 
739    final int getItemHeight(int idx) {
740       int result = prevwproc(LB_GETITEMHEIGHT, idx, 0);
741       if (LB_ERR == result) {
742          throw new DflException("Unable to obtain item height");
743       }
744       return result;
745    }
746 
747    final Rect getItemRectangle(int idx) {
748       RECT rect;
749       if (LB_ERR == prevwproc(LB_GETITEMRECT, idx, cast(LPARAM)&rect)) {
750          //if(idx >= 0 && idx < items.length)
751          return Rect(0, 0, 0, 0); // ?
752          //throw new DflException("Unable to obtain item rectangle");
753       }
754       return Rect(&rect);
755    }
756 
757    final bool getSelected(int idx) {
758       return prevwproc(LB_GETSEL, idx, 0) > 0;
759    }
760 
761    final int indexFromPoint(int x, int y) {
762       // LB_ITEMFROMPOINT is "nearest", so also check with the item rectangle to
763       // see if the point is directly in the item.
764 
765       // Maybe use LBItemFromPt() from common controls.
766 
767       int result = NO_MATCHES;
768 
769       if (created) {
770          result = prevwproc(LB_ITEMFROMPOINT, 0, MAKELPARAM(x, y));
771          if (!HIWORD(result)) { // In client area
772             //result = LOWORD(result); // High word already 0.
773             if (result < 0 || !getItemRectangle(result).contains(x, y)) {
774                result = NO_MATCHES;
775             }
776          } else { // Outside client area.
777             result = NO_MATCHES;
778          }
779       }
780 
781       return result;
782    }
783 
784    final int indexFromPoint(Point pt) {
785       return indexFromPoint(pt.x, pt.y);
786    }
787 
788    final void setSelected(int idx, bool byes) {
789       if (created) {
790          prevwproc(LB_SETSEL, byes, idx);
791       }
792    }
793 
794    protected ObjectCollection createItemCollection() {
795       return new ObjectCollection(this);
796    }
797 
798    void sort() {
799       if (icollection._items.length) {
800          Object[] itemscopy;
801          itemscopy = icollection._items.dup;
802          std.algorithm.sort(itemscopy);
803 
804          items.clear();
805 
806          beginUpdate();
807          scope (exit)
808             endUpdate();
809 
810          foreach (int i, Object o; itemscopy) {
811             items.insert(i, o);
812          }
813       }
814    }
815 
816    static class ObjectCollection {
817       protected this(ListBox lbox) {
818          this.lbox = lbox;
819       }
820 
821       protected this(ListBox lbox, Object[] range) {
822          this.lbox = lbox;
823          addRange(range);
824       }
825 
826       protected this(ListBox lbox, Dstring[] range) {
827          this.lbox = lbox;
828          addRange(range);
829       }
830 
831       /+
832       protected this(ListBox lbox, ObjectCollection range) {
833          this.lbox = lbox;
834          addRange(range);
835       }
836       +/
837 
838       void add(Object value) {
839          add2(value);
840       }
841 
842       void add(Dstring value) {
843          add(new ListString(value));
844       }
845 
846       void addRange(Object[] range) {
847          if (lbox.sorted) {
848             foreach (Object value; range) {
849                add(value);
850             }
851          } else {
852             _wraparray.addRange(range);
853          }
854       }
855 
856       void addRange(Dstring[] range) {
857          foreach (Dstring value; range) {
858             add(value);
859          }
860       }
861 
862    private:
863 
864       ListBox lbox;
865       Object[] _items;
866 
867       LRESULT insert2(WPARAM idx, Dstring val) {
868          insert(idx, val);
869          return idx;
870       }
871 
872       LRESULT add2(Object val) {
873          int i;
874          if (lbox.sorted) {
875             for (i = 0; i != _items.length; i++) {
876                if (val < _items[i]) {
877                   break;
878                }
879             }
880          } else {
881             i = _items.length;
882          }
883 
884          insert(i, val);
885 
886          return i;
887       }
888 
889       LRESULT add2(Dstring val) {
890          return add2(new ListString(val));
891       }
892 
893       void _added(size_t idx, Object val) {
894          if (lbox.created) {
895             if (dfl.internal.utf.useUnicode) {
896                lbox.prevwproc(LB_INSERTSTRING, idx,
897                   cast(LPARAM) dfl.internal.utf.toUnicodez(getObjectString(val)));
898             } else {
899                lbox.prevwproc(LB_INSERTSTRING, idx,
900                   cast(LPARAM) dfl.internal.utf.toAnsiz(getObjectString(val))); // Can this be unsafeAnsiz()?
901             }
902          }
903       }
904 
905       void _removed(size_t idx, Object val) {
906          if (size_t.max == idx) { // Clear all.
907             if (lbox.created) {
908                lbox.prevwproc(LB_RESETCONTENT, 0, 0);
909             }
910          } else {
911             if (lbox.created) {
912                lbox.prevwproc(LB_DELETESTRING, cast(WPARAM) idx, 0);
913             }
914          }
915       }
916 
917    public:
918 
919       mixin ListWrapArray!(Object, _items, _blankListCallback!(Object),
920          _added, _blankListCallback!(Object), _removed, true, false, false) _wraparray;
921    }
922 
923    this() {
924       _initListbox();
925 
926       // Default useTabStops and vertical scrolling.
927       wstyle |= WS_TABSTOP | LBS_USETABSTOPS | LBS_HASSTRINGS | WS_VSCROLL | LBS_NOTIFY;
928       wexstyle |= WS_EX_CLIENTEDGE;
929       ctrlStyle |= ControlStyles.SELECTABLE;
930       wclassStyle = listboxClassStyle;
931 
932       icollection = createItemCollection();
933       selidxcollection = new SelectedIndexCollection(this);
934       selobjcollection = new SelectedObjectCollection(this);
935    }
936 
937    protected override void onHandleCreated(EventArgs ea) {
938       super.onHandleCreated(ea);
939 
940       // Set the Ctrl ID to the HWND so that it is unique
941       // and WM_MEASUREITEM will work properly.
942       SetWindowLongA(hwnd, GWL_ID, cast(LONG) hwnd);
943 
944       if (hextent != 0) {
945          prevwproc(LB_SETHORIZONTALEXTENT, hextent, 0);
946       }
947 
948       if (iheight != DEFAULT_ITEM_HEIGHT) {
949          prevwproc(LB_SETITEMHEIGHT, 0, MAKELPARAM(iheight, 0));
950       }
951 
952       Message m;
953       m.hWnd = handle;
954       m.msg = LB_INSERTSTRING;
955       // Note: duplicate code.
956       if (dfl.internal.utf.useUnicode) {
957          foreach (int i, Object obj; icollection._items) {
958             m.wParam = i;
959             m.lParam = cast(LPARAM) dfl.internal.utf.toUnicodez(getObjectString(obj)); // <--
960 
961             prevWndProc(m);
962             //if(LB_ERR == m.result || LB_ERRSPACE == m.result)
963             if (m.result < 0) {
964                throw new DflException("Unable to add list item");
965             }
966 
967             //prevwproc(LB_SETITEMDATA, m.result, cast(LPARAM)cast(void*)obj);
968          }
969       } else {
970          foreach (int i, Object obj; icollection._items) {
971             m.wParam = i;
972             m.lParam = cast(LPARAM) dfl.internal.utf.toAnsiz(getObjectString(obj)); // Can this be unsafeAnsiz? // <--
973 
974             prevWndProc(m);
975             //if(LB_ERR == m.result || LB_ERRSPACE == m.result)
976             if (m.result < 0) {
977                throw new DflException("Unable to add list item");
978             }
979 
980             //prevwproc(LB_SETITEMDATA, m.result, cast(LPARAM)cast(void*)obj);
981          }
982       }
983 
984       //redrawEntire();
985    }
986 
987    /+
988    override void createHandle() {
989       if(isHandleCreated) {
990          return;
991       }
992 
993       createClassHandle(LISTBOX_CLASSNAME);
994 
995       onHandleCreated(EventArgs.empty);
996    }
997    +/
998 
999    protected override void createParams(ref CreateParams cp) {
1000       super.createParams(cp);
1001 
1002       cp.className = LISTBOX_CLASSNAME;
1003    }
1004 
1005    //DrawItemEventHandler drawItem;
1006    Event!(ListBox, DrawItemEventArgs) drawItem;
1007    //MeasureItemEventHandler measureItem;
1008    Event!(ListBox, MeasureItemEventArgs) measureItem;
1009 
1010 protected:
1011 
1012    void onDrawItem(DrawItemEventArgs dieh) {
1013       drawItem(this, dieh);
1014    }
1015 
1016    void onMeasureItem(MeasureItemEventArgs miea) {
1017       measureItem(this, miea);
1018    }
1019 
1020    package final void _WmDrawItem(DRAWITEMSTRUCT* dis)
1021    in {
1022       assert(dis.hwndItem == handle);
1023       assert(dis.CtlType == ODT_LISTBOX);
1024    }
1025    body {
1026       DrawItemState state;
1027       state = cast(DrawItemState) dis.itemState;
1028 
1029       if (dis.itemID == -1) {
1030          FillRect(dis.hDC, &dis.rcItem, hbrBg);
1031          if (state & DrawItemState.FOCUS) {
1032             DrawFocusRect(dis.hDC, &dis.rcItem);
1033          }
1034       } else {
1035          DrawItemEventArgs diea;
1036          Color bc, fc;
1037 
1038          if (state & DrawItemState.SELECTED) {
1039             bc = Color.systemColor(COLOR_HIGHLIGHT);
1040             fc = Color.systemColor(COLOR_HIGHLIGHTTEXT);
1041          } else {
1042             bc = backColor;
1043             fc = foreColor;
1044          }
1045 
1046          prepareDc(dis.hDC);
1047          diea = new DrawItemEventArgs(new Graphics(dis.hDC, false), wfont,
1048             Rect(&dis.rcItem), dis.itemID, state, fc, bc);
1049 
1050          onDrawItem(diea);
1051       }
1052    }
1053 
1054    package final void _WmMeasureItem(MEASUREITEMSTRUCT* mis)
1055    in {
1056       assert(mis.CtlType == ODT_LISTBOX);
1057    }
1058    body {
1059       MeasureItemEventArgs miea;
1060       scope Graphics gpx = new CommonGraphics(handle(), GetDC(handle));
1061       miea = new MeasureItemEventArgs(gpx, mis.itemID, /+ mis.itemHeight +/ iheight);
1062       miea.itemWidth = mis.itemWidth;
1063 
1064       onMeasureItem(miea);
1065 
1066       mis.itemHeight = miea.itemHeight;
1067       mis.itemWidth = miea.itemWidth;
1068    }
1069 
1070    override void prevWndProc(ref Message msg) {
1071       //msg.result = CallWindowProcA(listboxPrevWndProc, msg.hWnd, msg.msg, msg.wParam, msg.lParam);
1072       msg.result = dfl.internal.utf.callWindowProc(listboxPrevWndProc, msg.hWnd,
1073          msg.msg, msg.wParam, msg.lParam);
1074    }
1075 
1076    protected override void onReflectedMessage(ref Message m) {
1077       super.onReflectedMessage(m);
1078 
1079       switch (m.msg) {
1080       case WM_DRAWITEM:
1081          _WmDrawItem(cast(DRAWITEMSTRUCT*) m.lParam);
1082          m.result = 1;
1083          break;
1084 
1085       case WM_MEASUREITEM:
1086          _WmMeasureItem(cast(MEASUREITEMSTRUCT*) m.lParam);
1087          m.result = 1;
1088          break;
1089 
1090       case WM_COMMAND:
1091          assert(cast(HWND) m.lParam == handle);
1092          switch (HIWORD(m.wParam)) {
1093          case LBN_SELCHANGE:
1094             onSelectedIndexChanged(EventArgs.empty);
1095             break;
1096 
1097          case LBN_SELCANCEL:
1098             onSelectedIndexChanged(EventArgs.empty);
1099             break;
1100 
1101          default:
1102          }
1103          break;
1104 
1105       default:
1106       }
1107    }
1108 
1109    override void wndProc(ref Message msg) {
1110       switch (msg.msg) {
1111       case LB_ADDSTRING:
1112          //msg.result = icollection.add2(stringFromStringz(cast(char*)msg.lParam).dup); // TODO: fix.
1113          //msg.result = icollection.add2(stringFromStringz(cast(char*)msg.lParam).idup); // TODO: fix. // Needed in D2. Doesn't work in D1.
1114          msg.result = icollection.add2(cast(Dstring) stringFromStringz(cast(char*) msg.lParam).dup); // TODO: fix. // Needed in D2.
1115          return;
1116 
1117       case LB_INSERTSTRING:
1118          //msg.result = icollection.insert2(msg.wParam, stringFromStringz(cast(char*)msg.lParam).dup); // TODO: fix.
1119          //msg.result = icollection.insert2(msg.wParam, stringFromStringz(cast(char*)msg.lParam).idup); // TODO: fix. // Needed in D2. Doesn't work in D1.
1120          msg.result = icollection.insert2(msg.wParam,
1121             cast(Dstring) stringFromStringz(cast(char*) msg.lParam).dup); // TODO: fix. // Needed in D2.
1122          return;
1123 
1124       case LB_DELETESTRING:
1125          icollection.removeAt(msg.wParam);
1126          msg.result = icollection.length;
1127          return;
1128 
1129       case LB_RESETCONTENT:
1130          icollection.clear();
1131          return;
1132 
1133       case LB_SETITEMDATA:
1134          // Cannot set item data from outside DFL.
1135          msg.result = LB_ERR;
1136          return;
1137 
1138       case LB_ADDFILE:
1139          msg.result = LB_ERR;
1140          return;
1141 
1142       case LB_DIR:
1143          msg.result = LB_ERR;
1144          return;
1145 
1146       default:
1147       }
1148       super.wndProc(msg);
1149    }
1150 
1151 private:
1152    int hextent = 0;
1153    int iheight = DEFAULT_ITEM_HEIGHT;
1154    ObjectCollection icollection;
1155    SelectedIndexCollection selidxcollection;
1156    SelectedObjectCollection selobjcollection;
1157    bool _sorting = false;
1158 
1159 package:
1160 final:
1161    LRESULT prevwproc(UINT msg, WPARAM wparam, LPARAM lparam) {
1162       //return CallWindowProcA(listviewPrevWndProc, hwnd, msg, wparam, lparam);
1163       return dfl.internal.utf.callWindowProc(listboxPrevWndProc, hwnd, msg, wparam,
1164          lparam);
1165    }
1166 }