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