1 // Written by Christopher E. Miller
2 // See the included license.txt for copyright and license details.
3 
4 
5 
6 module dfl.combobox;
7 
8 private import dfl.internal.dlib;
9 
10 private import dfl.listbox, dfl.application, dfl.base, dfl.internal.winapi;
11 private import dfl.event, dfl.drawing, dfl.collections, dfl.control,
12         dfl.internal.utf;
13 
14 
15 private extern(Windows) void _initCombobox();
16 
17 
18 
19 enum ComboBoxStyle: ubyte {
20    DROP_DOWN, ///
21    DROP_DOWN_LIST, /// ditto
22    SIMPLE, /// ditto
23 }
24 
25 
26 
27 class ComboBox: ListControl { // docmain
28    this() {
29       _initCombobox();
30 
31       wstyle |= WS_TABSTOP | WS_VSCROLL | CBS_DROPDOWN | CBS_AUTOHSCROLL | CBS_HASSTRINGS;
32       wexstyle |= WS_EX_CLIENTEDGE;
33       ctrlStyle |= ControlStyles.SELECTABLE;
34       wclassStyle = comboboxClassStyle;
35 
36       icollection = createItemCollection();
37    }
38 
39 
40 
41    final @property void dropDownStyle(ComboBoxStyle ddstyle) { // setter
42       LONG st;
43       st = _style() & ~(CBS_DROPDOWN | CBS_DROPDOWNLIST | CBS_SIMPLE);
44 
45       final switch(ddstyle) {
46          case ComboBoxStyle.DROP_DOWN:
47             _style(st | CBS_DROPDOWN);
48             break;
49 
50          case ComboBoxStyle.DROP_DOWN_LIST:
51             _style(st | CBS_DROPDOWNLIST);
52             break;
53 
54          case ComboBoxStyle.SIMPLE:
55             _style(st | CBS_SIMPLE);
56             break;
57       }
58 
59       _crecreate();
60    }
61 
62    /// ditto
63    final @property ComboBoxStyle dropDownStyle() { // getter
64       LONG st;
65       st = _style() & (CBS_DROPDOWN | CBS_DROPDOWNLIST | CBS_SIMPLE);
66 
67       switch(st) {
68          case CBS_DROPDOWN:
69             return ComboBoxStyle.DROP_DOWN;
70 
71          case CBS_DROPDOWNLIST:
72             return ComboBoxStyle.DROP_DOWN_LIST;
73 
74          case CBS_SIMPLE:
75             return ComboBoxStyle.SIMPLE;
76          default:
77             assert(0);
78       }
79    }
80 
81 
82 
83    final @property void integralHeight(bool byes) { //setter
84       if(byes) {
85          _style(_style() & ~CBS_NOINTEGRALHEIGHT);
86       } else {
87          _style(_style() | CBS_NOINTEGRALHEIGHT);
88       }
89 
90       _crecreate();
91    }
92 
93    /// ditto
94    final @property bool integralHeight() { // getter
95       return (_style() & CBS_NOINTEGRALHEIGHT) == 0;
96    }
97 
98 
99 
100    // This function has no effect if the drawMode is OWNER_DRAW_VARIABLE.
101    @property void itemHeight(int h) { // setter
102       if(drawMode == DrawMode.OWNER_DRAW_VARIABLE) {
103          return;
104       }
105 
106       iheight = h;
107 
108       if(isHandleCreated) {
109          prevwproc(CB_SETITEMHEIGHT, 0, h);
110       }
111    }
112 
113    /// ditto
114    // Return value is meaningless when drawMode is OWNER_DRAW_VARIABLE.
115    @property int itemHeight() { // getter
116       /+
117       if(drawMode == DrawMode.OWNER_DRAW_VARIABLE || !isHandleCreated) {
118          return iheight;
119       }
120 
121       int result = prevwproc(CB_GETITEMHEIGHT, 0, 0);
122       if(result == CB_ERR) {
123          result = iheight;   // ?
124       } else {
125          iheight = result;
126       }
127 
128       return result;
129       +/
130       return iheight;
131    }
132 
133 
134 
135    override @property void selectedIndex(int idx) { // setter
136       if(isHandleCreated) {
137          prevwproc(CB_SETCURSEL, cast(WPARAM)idx, 0);
138       }
139    }
140 
141    /// ditto
142    override @property int selectedIndex() { //getter
143       if(isHandleCreated) {
144          LRESULT result;
145          result = prevwproc(CB_GETCURSEL, 0, 0);
146          if(CB_ERR != result) { // Redundant.
147             return cast(int)result;
148          }
149       }
150       return -1;
151    }
152 
153 
154 
155    final @property void selectedItem(Object o) { // setter
156       int i;
157       i = items.indexOf(o);
158       if(i != -1) {
159          selectedIndex = i;
160       }
161    }
162 
163    /// ditto
164    final @property void selectedItem(Dstring str) { // setter
165       int i;
166       i = items.indexOf(str);
167       if(i != -1) {
168          selectedIndex = i;
169       }
170    }
171 
172    /// ditto
173    final @property Object selectedItem() { // getter
174       int idx;
175       idx = selectedIndex;
176       if(idx == -1) {
177          return null;
178       }
179       return items[idx];
180    }
181 
182 
183 
184    override @property void selectedValue(Object val) { // setter
185       selectedItem = val;
186    }
187 
188    /// ditto
189    override @property void selectedValue(Dstring str) { // setter
190       selectedItem = str;
191    }
192 
193    /// ditto
194    override @property Object selectedValue() { // getter
195       return selectedItem;
196    }
197 
198 
199 
200    final @property void sorted(bool byes) { // setter
201       /+
202       if(byes) {
203          _style(_style() | CBS_SORT);
204       } else {
205          _style(_style() & ~CBS_SORT);
206       }
207       +/
208       _sorting = byes;
209    }
210 
211    /// ditto
212    final @property bool sorted() { // getter
213       //return (_style() & CBS_SORT) != 0;
214       return _sorting;
215    }
216 
217 
218 
219    final void beginUpdate() {
220       prevwproc(WM_SETREDRAW, false, 0);
221    }
222 
223    /// ditto
224    final void endUpdate() {
225       prevwproc(WM_SETREDRAW, true, 0);
226       invalidate(true); // Show updates.
227    }
228 
229 
230 
231    final int findString(Dstring str, int startIndex) {
232       // TODO: find string if control not created ?
233 
234       int result = NO_MATCHES;
235 
236       if(isHandleCreated) {
237          if(dfl.internal.utf.useUnicode) {
238             result = prevwproc(CB_FINDSTRING, startIndex, cast(LPARAM)dfl.internal.utf.toUnicodez(str));
239          } else {
240             result = prevwproc(CB_FINDSTRING, startIndex, cast(LPARAM)dfl.internal.utf.unsafeAnsiz(str));
241          }
242          if(result == CB_ERR) { // Redundant.
243             result = NO_MATCHES;
244          }
245       }
246 
247       return result;
248    }
249 
250    /// ditto
251    final int findString(Dstring str) {
252       return findString(str, -1); // Start at beginning.
253    }
254 
255 
256 
257    final int findStringExact(Dstring str, int startIndex) {
258       // TODO: find string if control not created ?
259 
260       int result = NO_MATCHES;
261 
262       if(isHandleCreated) {
263          if(dfl.internal.utf.useUnicode) {
264             result = prevwproc(CB_FINDSTRINGEXACT, startIndex, cast(LPARAM)dfl.internal.utf.toUnicodez(str));
265          } else {
266             result = prevwproc(CB_FINDSTRINGEXACT, startIndex, cast(LPARAM)dfl.internal.utf.unsafeAnsiz(str));
267          }
268          if(result == CB_ERR) { // Redundant.
269             result = NO_MATCHES;
270          }
271       }
272 
273       return result;
274    }
275 
276    /// ditto
277    final int findStringExact(Dstring str) {
278       return findStringExact(str, -1); // Start at beginning.
279    }
280 
281 
282 
283    final int getItemHeight(int idx) {
284       int result = prevwproc(CB_GETITEMHEIGHT, idx, 0);
285       if(CB_ERR == result) {
286          throw new DflException("Unable to obtain item height");
287       }
288       return result;
289    }
290 
291 
292 
293    final @property void drawMode(DrawMode dm) { // setter
294       LONG wl = _style() & ~(CBS_OWNERDRAWVARIABLE | CBS_OWNERDRAWFIXED);
295 
296       final switch(dm) {
297          case DrawMode.OWNER_DRAW_VARIABLE:
298             wl |= CBS_OWNERDRAWVARIABLE;
299             break;
300 
301          case DrawMode.OWNER_DRAW_FIXED:
302             wl |= CBS_OWNERDRAWFIXED;
303             break;
304 
305          case DrawMode.NORMAL:
306             break;
307       }
308 
309       _style(wl);
310 
311       _crecreate();
312    }
313 
314    /// ditto
315    final @property DrawMode drawMode() { // getter
316       LONG wl = _style();
317 
318       if(wl & CBS_OWNERDRAWVARIABLE) {
319          return DrawMode.OWNER_DRAW_VARIABLE;
320       }
321       if(wl & CBS_OWNERDRAWFIXED) {
322          return DrawMode.OWNER_DRAW_FIXED;
323       }
324       return DrawMode.NORMAL;
325    }
326 
327 
328 
329    final void selectAll() {
330       if(isHandleCreated) {
331          prevwproc(CB_SETEDITSEL, 0, MAKELPARAM(0, cast(ushort)-1));
332       }
333    }
334 
335 
336 
337    final @property void maxLength(uint len) { // setter
338       if(!len) {
339          lim = 0x7FFFFFFE;
340       } else {
341          lim = len;
342       }
343 
344       if(isHandleCreated) {
345          Message m;
346          m = Message(handle, CB_LIMITTEXT, cast(WPARAM)lim, 0);
347          prevWndProc(m);
348       }
349    }
350 
351    /// ditto
352    final @property uint maxLength() { // getter
353       return lim;
354    }
355 
356 
357 
358    final @property void selectionLength(uint len) { // setter
359       if(isHandleCreated) {
360          uint v1, v2;
361          prevwproc(CB_GETEDITSEL, cast(WPARAM)&v1, cast(LPARAM)&v2);
362          v2 = v1 + len;
363          prevwproc(CB_SETEDITSEL, 0, MAKELPARAM(v1, v2));
364       }
365    }
366 
367    /// ditto
368    final @property uint selectionLength() { // getter
369       if(isHandleCreated) {
370          uint v1, v2;
371          prevwproc(CB_GETEDITSEL, cast(WPARAM)&v1, cast(LPARAM)&v2);
372          assert(v2 >= v1);
373          return v2 - v1;
374       }
375       return 0;
376    }
377 
378 
379 
380    final @property void selectionStart(uint pos) { // setter
381       if(isHandleCreated) {
382          uint v1, v2;
383          prevwproc(CB_GETEDITSEL, cast(WPARAM)&v1, cast(LPARAM)&v2);
384          assert(v2 >= v1);
385          v2 = pos + (v2 - v1);
386          prevwproc(CB_SETEDITSEL, 0, MAKELPARAM(pos, v2));
387       }
388    }
389 
390    /// ditto
391    final @property uint selectionStart() { // getter
392       if(isHandleCreated) {
393          uint v1, v2;
394          prevwproc(CB_GETEDITSEL, cast(WPARAM)&v1, cast(LPARAM)&v2);
395          return v1;
396       }
397       return 0;
398    }
399 
400 
401 
402    // Number of characters in the textbox.
403    // This does not necessarily correspond to the number of chars; some characters use multiple chars.
404    // Return may be larger than the amount of characters.
405    // This is a lot faster than retrieving the text, but retrieving the text is completely accurate.
406    @property uint textLength() { // getter
407       if(!(ctrlStyle & ControlStyles.CACHE_TEXT) && isHandleCreated)
408          //return cast(uint)SendMessageA(handle, WM_GETTEXTLENGTH, 0, 0);
409       {
410          return cast(uint)dfl.internal.utf.sendMessage(handle, WM_GETTEXTLENGTH, 0, 0);
411       }
412       return wtext.length;
413    }
414 
415 
416 
417    final @property void droppedDown(bool byes) { // setter
418       if(isHandleCreated) {
419          prevwproc(CB_SHOWDROPDOWN, cast(WPARAM)byes, 0);
420       }
421    }
422 
423    /// ditto
424    final @property bool droppedDown() { // getter
425       if(isHandleCreated) {
426          return prevwproc(CB_GETDROPPEDSTATE, 0, 0) != FALSE;
427       }
428       return false;
429    }
430 
431 
432 
433    final @property void dropDownWidth(int w) { // setter
434       if(dropw == w) {
435          return;
436       }
437 
438       if(w < 0) {
439          w = 0;
440       }
441       dropw = w;
442 
443       if(isHandleCreated) {
444          if(dropw < width) {
445             prevwproc(CB_SETDROPPEDWIDTH, width, 0);
446          } else {
447             prevwproc(CB_SETDROPPEDWIDTH, dropw, 0);
448          }
449       }
450    }
451 
452    /// ditto
453    final @property int dropDownWidth() { // getter
454       if(isHandleCreated) {
455          int w;
456          w = cast(int)prevwproc(CB_GETDROPPEDWIDTH, 0, 0);
457          if(dropw != -1) {
458             dropw = w;
459          }
460          return w;
461       } else {
462          if(dropw < width) {
463             return width;
464          }
465          return dropw;
466       }
467    }
468 
469 
470 
471    final @property ObjectCollection items() { // getter
472       return icollection;
473    }
474 
475 
476    enum DEFAULT_ITEM_HEIGHT = 13;
477    enum NO_MATCHES = CB_ERR;
478 
479 
480 
481    static class ObjectCollection {
482       protected this(ComboBox lbox) {
483          this.lbox = lbox;
484       }
485 
486 
487       protected this(ComboBox lbox, Object[] range) {
488          this.lbox = lbox;
489          addRange(range);
490       }
491 
492 
493       protected this(ComboBox lbox, Dstring[] range) {
494          this.lbox = lbox;
495          addRange(range);
496       }
497 
498 
499       /+
500       protected this(ComboBox lbox, ObjectCollection range) {
501          this.lbox = lbox;
502          addRange(range);
503       }
504       +/
505 
506 
507       void add(Object value) {
508          add2(value);
509       }
510 
511       void add(Dstring value) {
512          add(new ListString(value));
513       }
514 
515 
516       void addRange(Object[] range) {
517          if(lbox.sorted) {
518             foreach(Object value; range) {
519                add(value);
520             }
521          } else {
522             _wraparray.addRange(range);
523          }
524       }
525 
526       void addRange(Dstring[] range) {
527          foreach(Dstring s; range) {
528             add(s);
529          }
530       }
531 
532 
533     private:
534 
535       ComboBox lbox;
536       Object[] _items;
537 
538 
539       this() {
540       }
541 
542 
543       LRESULT insert2(WPARAM idx, Dstring val) {
544          insert(idx, val);
545          return idx;
546       }
547 
548 
549       LRESULT add2(Object val) {
550          int i;
551          if(lbox.sorted) {
552             for(i = 0; i != _items.length; i++) {
553                if(val < _items[i]) {
554                   break;
555                }
556             }
557          } else {
558             i = _items.length;
559          }
560 
561          insert(i, val);
562 
563          return i;
564       }
565 
566 
567       LRESULT add2(Dstring val) {
568          return add2(new ListString(val));
569       }
570 
571 
572       void _added(size_t idx, Object val) {
573          if(lbox.isHandleCreated) {
574             if(dfl.internal.utf.useUnicode) {
575                lbox.prevwproc(CB_INSERTSTRING, idx, cast(LPARAM)dfl.internal.utf.toUnicodez(getObjectString(val)));
576             } else {
577                lbox.prevwproc(CB_INSERTSTRING, idx, cast(LPARAM)dfl.internal.utf.toAnsiz(getObjectString(val)));   // Can this be unsafeAnsiz()?
578             }
579          }
580       }
581 
582 
583       void _removed(size_t idx, Object val) {
584          if(size_t.max == idx) { // Clear all.
585             if(lbox.isHandleCreated) {
586                lbox.prevwproc(CB_RESETCONTENT, 0, 0);
587             }
588          } else {
589             if(lbox.isHandleCreated) {
590                lbox.prevwproc(CB_DELETESTRING, cast(WPARAM)idx, 0);
591             }
592          }
593       }
594 
595 
596     public:
597 
598       mixin ListWrapArray!(Object, _items,
599                            _blankListCallback!(Object), _added,
600                            _blankListCallback!(Object), _removed,
601                            true, false, false) _wraparray;
602    }
603 
604 
605 
606    protected ObjectCollection createItemCollection() {
607       return new ObjectCollection(this);
608    }
609 
610 
611    protected override void onHandleCreated(EventArgs ea) {
612       super.onHandleCreated(ea);
613 
614       // Set the Ctrl ID to the HWND so that it is unique
615       // and WM_MEASUREITEM will work properly.
616       SetWindowLongA(hwnd, GWL_ID, cast(LONG)hwnd);
617 
618       //prevwproc(EM_SETLIMITTEXT, cast(WPARAM)lim, 0);
619       maxLength = lim; // Call virtual function.
620 
621       if(dropw < width) {
622          prevwproc(CB_SETDROPPEDWIDTH, width, 0);
623       } else {
624          prevwproc(CB_SETDROPPEDWIDTH, dropw, 0);
625       }
626 
627       if(iheight != DEFAULT_ITEM_HEIGHT) {
628          prevwproc(CB_SETITEMHEIGHT, 0, iheight);
629       }
630 
631       Message m;
632       m.hWnd = hwnd;
633       m.msg = CB_INSERTSTRING;
634       // Note: duplicate code.
635       if(dfl.internal.utf.useUnicode) {
636          foreach(int i, Object obj; icollection._items) {
637             m.wParam = i;
638             m.lParam = cast(LPARAM)dfl.internal.utf.toUnicodez(getObjectString(obj)); // <--
639 
640             prevWndProc(m);
641             //if(CB_ERR == m.result || CB_ERRSPACE == m.result)
642             if(m.result < 0) {
643                throw new DflException("Unable to add combo box item");
644             }
645 
646             //prevwproc(CB_SETITEMDATA, m.result, cast(LPARAM)cast(void*)obj);
647          }
648       } else {
649          foreach(int i, Object obj; icollection._items) {
650             m.wParam = i;
651             m.lParam = cast(LPARAM)dfl.internal.utf.toAnsiz(getObjectString(obj)); // Can this be unsafeAnsiz()? // <--
652 
653             prevWndProc(m);
654             //if(CB_ERR == m.result || CB_ERRSPACE == m.result)
655             if(m.result < 0) {
656                throw new DflException("Unable to add combo box item");
657             }
658 
659             //prevwproc(CB_SETITEMDATA, m.result, cast(LPARAM)cast(void*)obj);
660          }
661       }
662 
663       //redrawEntire();
664    }
665 
666 
667    package final @property bool hasDropList() { // getter
668       return dropDownStyle != ComboBoxStyle.SIMPLE;
669    }
670 
671 
672    // This is needed for the SIMPLE style.
673    protected override void onPaintBackground(PaintEventArgs pea) {
674       RECT rect;
675       pea.clipRectangle.getRect(&rect);
676       FillRect(pea.graphics.handle, &rect, parent.hbrBg); // Hack.
677    }
678 
679 
680    override void createHandle() {
681       if(isHandleCreated) {
682          return;
683       }
684 
685       // TODO: check if correct implementation.
686       if(hasDropList) {
687          wrect.height = DEFAULT_ITEM_HEIGHT * 8;
688       }
689 
690       Dstring ft;
691       ft = wtext;
692 
693       super.createHandle();
694 
695       // Fix the cached window rect.
696       // This is getting screen coords, not parent coords. Why was it here, anyway?
697       //RECT rect;
698       //GetWindowRect(hwnd, &rect);
699       //wrect = Rect(&rect);
700 
701       // Fix the combo box's text since the initial window
702       // text isn't put in the edit box for some reason.
703       Message m;
704       if(dfl.internal.utf.useUnicode) {
705          m = Message(hwnd, WM_SETTEXT, 0, cast(LPARAM)dfl.internal.utf.toUnicodez(ft));
706       } else {
707          m = Message(hwnd, WM_SETTEXT, 0, cast(LPARAM)dfl.internal.utf.toAnsiz(ft));   // Can this be unsafeAnsiz()?
708       }
709       prevWndProc(m);
710    }
711 
712 
713    protected override void createParams(ref CreateParams cp) {
714       super.createParams(cp);
715 
716       cp.className = COMBOBOX_CLASSNAME;
717    }
718 
719 
720    //DrawItemEventHandler drawItem;
721    Event!(ComboBox, DrawItemEventArgs) drawItem;
722    //MeasureItemEventHandler measureItem;
723    Event!(ComboBox, MeasureItemEventArgs) measureItem;
724 
725 
726  protected:
727    override @property Size defaultSize() { // getter
728       return Size(120, 23); // ?
729    }
730 
731 
732    void onDrawItem(DrawItemEventArgs dieh) {
733       drawItem(this, dieh);
734    }
735 
736 
737    void onMeasureItem(MeasureItemEventArgs miea) {
738       measureItem(this, miea);
739    }
740 
741 
742    package final void _WmDrawItem(DRAWITEMSTRUCT* dis)
743    in {
744       assert(dis.hwndItem == handle);
745       assert(dis.CtlType == ODT_COMBOBOX);
746    }
747    body {
748       DrawItemState state;
749       state = cast(DrawItemState)dis.itemState;
750 
751       if(dis.itemID == -1) {
752          if(state & DrawItemState.FOCUS) {
753             DrawFocusRect(dis.hDC, &dis.rcItem);
754          }
755       } else
756       {
757          DrawItemEventArgs diea;
758          Color bc, fc;
759 
760          if(state & DrawItemState.SELECTED) {
761             bc = Color.systemColor(COLOR_HIGHLIGHT);
762             fc = Color.systemColor(COLOR_HIGHLIGHTTEXT);
763          } else
764          {
765             bc = backColor;
766             fc = foreColor;
767          }
768 
769          prepareDc(dis.hDC);
770          diea = new DrawItemEventArgs(new Graphics(dis.hDC, false), wfont,
771          Rect(&dis.rcItem), dis.itemID, state, fc, bc);
772 
773          onDrawItem(diea);
774       }
775    }
776 
777 
778    package final void _WmMeasureItem(MEASUREITEMSTRUCT* mis)
779    in {
780       assert(mis.CtlType == ODT_COMBOBOX);
781    }
782    body {
783       MeasureItemEventArgs miea;
784       scope Graphics gpx = new CommonGraphics(handle(), GetDC(handle));
785       miea = new MeasureItemEventArgs(gpx, mis.itemID, /+ mis.itemHeight +/ iheight);
786       miea.itemWidth = mis.itemWidth;
787 
788       onMeasureItem(miea);
789 
790       mis.itemHeight = miea.itemHeight;
791       mis.itemWidth = miea.itemWidth;
792    }
793 
794 
795    override void prevWndProc(ref Message msg) {
796       //msg.result = CallWindowProcA(comboboxPrevWndProc, msg.hWnd, msg.msg, msg.wParam, msg.lParam);
797       msg.result = dfl.internal.utf.callWindowProc(comboboxPrevWndProc, msg.hWnd, msg.msg, msg.wParam, msg.lParam);
798    }
799 
800 
801    protected override void onReflectedMessage(ref Message m) {
802       super.onReflectedMessage(m);
803 
804       switch(m.msg) {
805          case WM_DRAWITEM:
806             _WmDrawItem(cast(DRAWITEMSTRUCT*)m.lParam);
807             m.result = 1;
808             break;
809 
810          case WM_MEASUREITEM:
811             _WmMeasureItem(cast(MEASUREITEMSTRUCT*)m.lParam);
812             m.result = 1;
813             break;
814 
815             /+
816          case WM_CTLCOLORSTATIC:
817          case WM_CTLCOLOREDIT:
818             /+
819             //SetBkColor(cast(HDC)m.wParam, backColor.toRgb()); // ?
820             SetBkMode(cast(HDC)m.wParam, OPAQUE); // ?
821             +/
822             break;
823             +/
824 
825          case WM_COMMAND:
826             //assert(cast(HWND)msg.lParam == handle); // Might be one of its children.
827             switch(HIWORD(m.wParam)) {
828                case CBN_SELCHANGE:
829                   /+
830                   if(drawMode != DrawMode.NORMAL) {
831                      // Hack.
832                      Object item = selectedItem;
833                      text = item ? getObjectString(item) : cast(Dstring)null;
834                   }
835                   +/
836                   onSelectedIndexChanged(EventArgs.empty);
837                   onTextChanged(EventArgs.empty); // ?
838                   break;
839 
840                case CBN_SETFOCUS:
841                   _wmSetFocus();
842                   break;
843 
844                case CBN_KILLFOCUS:
845                   _wmKillFocus();
846                   break;
847 
848                case CBN_EDITCHANGE:
849                   onTextChanged(EventArgs.empty); // ?
850                   break;
851 
852                default:
853             }
854             break;
855 
856          default:
857       }
858    }
859 
860 
861    override void wndProc(ref Message msg) {
862       switch(msg.msg) {
863          case CB_ADDSTRING:
864             //msg.result = icollection.add2(stringFromStringz(cast(char*)msg.lParam).dup); // TODO: fix.
865             //msg.result = icollection.add2(stringFromStringz(cast(char*)msg.lParam).idup); // TODO: fix. // Needed in D2. Doesn't work in D1.
866             msg.result = icollection.add2(cast(Dstring)stringFromStringz(cast(char*)msg.lParam).dup); // TODO: fix. // Needed in D2.
867             return;
868 
869          case CB_INSERTSTRING:
870             //msg.result = icollection.insert2(msg.wParam, stringFromStringz(cast(char*)msg.lParam).dup); // TODO: fix.
871             //msg.result = icollection.insert2(msg.wParam, stringFromStringz(cast(char*)msg.lParam).idup); // TODO: fix. // Needed in D2. Doesn't work in D1.
872             msg.result = icollection.insert2(msg.wParam, cast(Dstring)stringFromStringz(cast(char*)msg.lParam).dup); // TODO: fix. // Needed in D2.
873             return;
874 
875          case CB_DELETESTRING:
876             icollection.removeAt(msg.wParam);
877             msg.result = icollection.length;
878             return;
879 
880          case CB_RESETCONTENT:
881             icollection.clear();
882             return;
883 
884          case CB_SETITEMDATA:
885             // Cannot set item data from outside DFL.
886             msg.result = CB_ERR;
887             return;
888 
889          case CB_DIR:
890             msg.result = CB_ERR;
891             return;
892 
893          case CB_LIMITTEXT:
894             maxLength = msg.wParam;
895             return;
896 
897          case WM_SETFOCUS:
898          case WM_KILLFOCUS:
899             prevWndProc(msg);
900             return; // Handled by reflected message.
901 
902          default:
903       }
904       super.wndProc(msg);
905    }
906 
907 
908  private:
909    int iheight = DEFAULT_ITEM_HEIGHT;
910    int dropw = -1;
911    ObjectCollection icollection;
912    package uint lim = 30_000; // Documented as default.
913    bool _sorting = false;
914 
915 
916  package:
917  final:
918    LRESULT prevwproc(UINT msg, WPARAM wparam, LPARAM lparam) {
919       //return CallWindowProcA(listviewPrevWndProc, hwnd, msg, wparam, lparam);
920       return dfl.internal.utf.callWindowProc(comboboxPrevWndProc, hwnd, msg, wparam, lparam);
921    }
922 }
923