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