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