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