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